mirror of
http://git.haproxy.org/git/haproxy.git/
synced 2025-02-19 04:07:04 +00:00
MEDIUM: stats: add proxy name filtering on the statistic page
This patch adds a "scope" box in the statistics page in order to display only proxies with a name that contains the requested value. The scope filter is preserved across all clicks on the page.
This commit is contained in:
parent
d9bdccda55
commit
88c278fadf
@ -745,4 +745,7 @@ char *env_expand(char *in);
|
||||
*/
|
||||
#define fddebug(msg...) do { char *_m = NULL; memprintf(&_m, ##msg); if (_m) write(-1, _m, strlen(_m)); free(_m); } while (0)
|
||||
|
||||
/* same as strstr() but case-insensitive */
|
||||
const char *strnistr(const char *str1, int len_str1, const char *str2, int len_str2);
|
||||
|
||||
#endif /* _COMMON_STANDARD_H */
|
||||
|
@ -54,6 +54,11 @@
|
||||
#define STAT_CLI_O_SET 10 /* set entries in tables */
|
||||
#define STAT_CLI_O_STAT 11 /* dump stats */
|
||||
|
||||
/* HTML form to limit output scope */
|
||||
#define STAT_SCOPE_TXT_MAXLEN 20 /* max len for scope substring */
|
||||
#define STAT_SCOPE_INPUT_NAME "scope" /* pattern form scope name <input> in html form */
|
||||
#define STAT_SCOPE_PATTERN "?" STAT_SCOPE_INPUT_NAME "="
|
||||
|
||||
extern struct si_applet http_stats_applet;
|
||||
|
||||
void stats_io_handler(struct stream_interface *si);
|
||||
|
@ -124,6 +124,8 @@ struct stream_interface {
|
||||
struct proxy *px;
|
||||
struct server *sv;
|
||||
void *l;
|
||||
int scope_str; /* limit scope to a frontend/backend substring */
|
||||
int scope_len; /* length of the string above in the buffer */
|
||||
int px_st; /* STAT_PX_ST* */
|
||||
unsigned int flags; /* STAT_* */
|
||||
int iid, type, sid; /* proxy id, type and service id if bounding of stats is enabled */
|
||||
|
102
src/dumpstats.c
102
src/dumpstats.c
@ -2695,11 +2695,25 @@ static int stats_dump_be_stats(struct stream_interface *si, struct proxy *px, in
|
||||
*/
|
||||
static void stats_dump_html_px_hdr(struct stream_interface *si, struct proxy *px, struct uri_auth *uri)
|
||||
{
|
||||
char scope_txt[STAT_SCOPE_TXT_MAXLEN + sizeof STAT_SCOPE_PATTERN];
|
||||
|
||||
if (px->cap & PR_CAP_BE && px->srv && (si->applet.ctx.stats.flags & STAT_ADMIN)) {
|
||||
/* A form to enable/disable this proxy servers */
|
||||
|
||||
/* scope_txt = search pattern + search query, si->applet.ctx.stats.scope_len is always <= STAT_SCOPE_TXT_MAXLEN */
|
||||
scope_txt[0] = 0;
|
||||
if (si->applet.ctx.stats.scope_len) {
|
||||
strcpy(scope_txt, STAT_SCOPE_PATTERN);
|
||||
memcpy(scope_txt + strlen(STAT_SCOPE_PATTERN), bo_ptr(si->ob->buf) + si->applet.ctx.stats.scope_str, si->applet.ctx.stats.scope_len);
|
||||
scope_txt[strlen(STAT_SCOPE_PATTERN) + si->applet.ctx.stats.scope_len] = 0;
|
||||
}
|
||||
|
||||
chunk_appendf(&trash,
|
||||
"<form action=\"%s\" method=\"post\">",
|
||||
uri->uri_prefix);
|
||||
"<form action=\"%s%s%s%s\" method=\"post\">",
|
||||
uri->uri_prefix,
|
||||
(si->applet.ctx.stats.flags & STAT_HIDE_DOWN) ? ";up" : "",
|
||||
(si->applet.ctx.stats.flags & STAT_NO_REFRESH) ? ";norefresh" : "",
|
||||
scope_txt);
|
||||
}
|
||||
|
||||
/* print a new table */
|
||||
@ -2768,19 +2782,19 @@ static void stats_dump_html_px_end(struct stream_interface *si, struct proxy *px
|
||||
if ((px->cap & PR_CAP_BE) && px->srv && (si->applet.ctx.stats.flags & STAT_ADMIN)) {
|
||||
/* close the form used to enable/disable this proxy servers */
|
||||
chunk_appendf(&trash,
|
||||
"Choose the action to perform on the checked servers : "
|
||||
"<select name=action>"
|
||||
"<option value=\"\"></option>"
|
||||
"<option value=\"disable\">Disable</option>"
|
||||
"<option value=\"enable\">Enable</option>"
|
||||
"<option value=\"stop\">Soft Stop</option>"
|
||||
"<option value=\"start\">Soft Start</option>"
|
||||
"<option value=\"shutdown\">Kill Sessions</option>"
|
||||
"</select>"
|
||||
"<input type=\"hidden\" name=\"b\" value=\"#%d\">"
|
||||
" <input type=\"submit\" value=\"Apply\">"
|
||||
"</form>",
|
||||
px->uuid);
|
||||
"Choose the action to perform on the checked servers : "
|
||||
"<select name=action>"
|
||||
"<option value=\"\"></option>"
|
||||
"<option value=\"disable\">Disable</option>"
|
||||
"<option value=\"enable\">Enable</option>"
|
||||
"<option value=\"stop\">Soft Stop</option>"
|
||||
"<option value=\"start\">Soft Start</option>"
|
||||
"<option value=\"shutdown\">Kill Sessions</option>"
|
||||
"</select>"
|
||||
"<input type=\"hidden\" name=\"b\" value=\"#%d\">"
|
||||
" <input type=\"submit\" value=\"Apply\">"
|
||||
"</form>",
|
||||
px->uuid);
|
||||
}
|
||||
|
||||
chunk_appendf(&trash, "<p>\n");
|
||||
@ -2829,6 +2843,13 @@ static int stats_dump_proxy_to_buffer(struct stream_interface *si, struct proxy
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* if the user has requested a limited output and the proxy
|
||||
* name does not match, skip it.
|
||||
*/
|
||||
if (si->applet.ctx.stats.scope_len &&
|
||||
strnistr(px->id, strlen(px->id), bo_ptr(si->ob->buf) + si->applet.ctx.stats.scope_str, si->applet.ctx.stats.scope_len) == NULL)
|
||||
return 1;
|
||||
|
||||
if ((si->applet.ctx.stats.flags & STAT_BOUND) &&
|
||||
(si->applet.ctx.stats.iid != -1) &&
|
||||
(px->uuid != si->applet.ctx.stats.iid))
|
||||
@ -3092,6 +3113,7 @@ static void stats_dump_html_head(struct uri_auth *uri)
|
||||
static void stats_dump_html_info(struct stream_interface *si, struct uri_auth *uri)
|
||||
{
|
||||
unsigned int up = (now.tv_sec - start_date.tv_sec);
|
||||
char scope_txt[STAT_SCOPE_TXT_MAXLEN + sizeof STAT_SCOPE_PATTERN];
|
||||
|
||||
/* WARNING! this has to fit the first packet too.
|
||||
* We are around 3.5 kB, add adding entries will
|
||||
@ -3147,44 +3169,70 @@ static void stats_dump_html_info(struct stream_interface *si, struct uri_auth *u
|
||||
run_queue_cur, nb_tasks_cur, idle_pct
|
||||
);
|
||||
|
||||
/* scope_txt = search query, si->applet.ctx.stats.scope_len is always <= STAT_SCOPE_TXT_MAXLEN */
|
||||
memcpy(scope_txt, bo_ptr(si->ob->buf) + si->applet.ctx.stats.scope_str, si->applet.ctx.stats.scope_len);
|
||||
scope_txt[si->applet.ctx.stats.scope_len] = '\0';
|
||||
|
||||
chunk_appendf(&trash,
|
||||
"<li><form method=GET ACTION='%s%s%s'>Scope : <input value='%s' name='" STAT_SCOPE_INPUT_NAME "' autofocus size=8 maxlength='%d'/></form>\n",
|
||||
uri->uri_prefix,
|
||||
(si->applet.ctx.stats.flags & STAT_HIDE_DOWN) ? ";up" : "",
|
||||
(si->applet.ctx.stats.flags & STAT_NO_REFRESH) ? ";norefresh" : "",
|
||||
(si->applet.ctx.stats.scope_len > 0) ? scope_txt : "",
|
||||
STAT_SCOPE_TXT_MAXLEN);
|
||||
|
||||
/* scope_txt = search pattern + search query, si->applet.ctx.stats.scope_len is always <= STAT_SCOPE_TXT_MAXLEN */
|
||||
scope_txt[0] = 0;
|
||||
if (si->applet.ctx.stats.scope_len) {
|
||||
strcpy(scope_txt, STAT_SCOPE_PATTERN);
|
||||
memcpy(scope_txt + strlen(STAT_SCOPE_PATTERN), bo_ptr(si->ob->buf) + si->applet.ctx.stats.scope_str, si->applet.ctx.stats.scope_len);
|
||||
scope_txt[strlen(STAT_SCOPE_PATTERN) + si->applet.ctx.stats.scope_len] = 0;
|
||||
}
|
||||
|
||||
if (si->applet.ctx.stats.flags & STAT_HIDE_DOWN)
|
||||
chunk_appendf(&trash,
|
||||
"<li><a href=\"%s%s%s\">Show all servers</a><br>\n",
|
||||
"<li><a href=\"%s%s%s%s\">Show all servers</a><br>\n",
|
||||
uri->uri_prefix,
|
||||
"",
|
||||
(si->applet.ctx.stats.flags & STAT_NO_REFRESH) ? ";norefresh" : "");
|
||||
(si->applet.ctx.stats.flags & STAT_NO_REFRESH) ? ";norefresh" : "",
|
||||
scope_txt);
|
||||
else
|
||||
chunk_appendf(&trash,
|
||||
"<li><a href=\"%s%s%s\">Hide 'DOWN' servers</a><br>\n",
|
||||
"<li><a href=\"%s%s%s%s\">Hide 'DOWN' servers</a><br>\n",
|
||||
uri->uri_prefix,
|
||||
";up",
|
||||
(si->applet.ctx.stats.flags & STAT_NO_REFRESH) ? ";norefresh" : "");
|
||||
(si->applet.ctx.stats.flags & STAT_NO_REFRESH) ? ";norefresh" : "",
|
||||
scope_txt);
|
||||
|
||||
if (uri->refresh > 0) {
|
||||
if (si->applet.ctx.stats.flags & STAT_NO_REFRESH)
|
||||
chunk_appendf(&trash,
|
||||
"<li><a href=\"%s%s%s\">Enable refresh</a><br>\n",
|
||||
"<li><a href=\"%s%s%s%s\">Enable refresh</a><br>\n",
|
||||
uri->uri_prefix,
|
||||
(si->applet.ctx.stats.flags & STAT_HIDE_DOWN) ? ";up" : "",
|
||||
"");
|
||||
"",
|
||||
scope_txt);
|
||||
else
|
||||
chunk_appendf(&trash,
|
||||
"<li><a href=\"%s%s%s\">Disable refresh</a><br>\n",
|
||||
"<li><a href=\"%s%s%s%s\">Disable refresh</a><br>\n",
|
||||
uri->uri_prefix,
|
||||
(si->applet.ctx.stats.flags & STAT_HIDE_DOWN) ? ";up" : "",
|
||||
";norefresh");
|
||||
";norefresh",
|
||||
scope_txt);
|
||||
}
|
||||
|
||||
chunk_appendf(&trash,
|
||||
"<li><a href=\"%s%s%s\">Refresh now</a><br>\n",
|
||||
"<li><a href=\"%s%s%s%s\">Refresh now</a><br>\n",
|
||||
uri->uri_prefix,
|
||||
(si->applet.ctx.stats.flags & STAT_HIDE_DOWN) ? ";up" : "",
|
||||
(si->applet.ctx.stats.flags & STAT_NO_REFRESH) ? ";norefresh" : "");
|
||||
(si->applet.ctx.stats.flags & STAT_NO_REFRESH) ? ";norefresh" : "",
|
||||
scope_txt);
|
||||
|
||||
chunk_appendf(&trash,
|
||||
"<li><a href=\"%s;csv%s\">CSV export</a><br>\n",
|
||||
"<li><a href=\"%s;csv%s%s\">CSV export</a><br>\n",
|
||||
uri->uri_prefix,
|
||||
(uri->refresh > 0) ? ";norefresh" : "");
|
||||
(uri->refresh > 0) ? ";norefresh" : "",
|
||||
scope_txt);
|
||||
|
||||
chunk_appendf(&trash,
|
||||
"</ul></td>"
|
||||
|
@ -2971,6 +2971,8 @@ int http_handle_stats(struct session *s, struct channel *req)
|
||||
|
||||
/* Was the status page requested with a POST ? */
|
||||
if (unlikely(txn->meth == HTTP_METH_POST)) {
|
||||
char scope_txt[STAT_SCOPE_TXT_MAXLEN + sizeof STAT_SCOPE_PATTERN];
|
||||
|
||||
if (si->applet.ctx.stats.flags & STAT_ADMIN) {
|
||||
if (msg->msg_state < HTTP_MSG_100_SENT) {
|
||||
/* If we have HTTP/1.1 and Expect: 100-continue, then we must
|
||||
@ -2993,6 +2995,14 @@ int http_handle_stats(struct session *s, struct channel *req)
|
||||
}
|
||||
else
|
||||
si->applet.ctx.stats.st_code = STAT_STATUS_DENY;
|
||||
/* scope_txt = search pattern + search query, si->applet.ctx.stats.scope_len is always <= STAT_SCOPE_TXT_MAXLEN */
|
||||
scope_txt[0] = 0;
|
||||
if (si->applet.ctx.stats.scope_len) {
|
||||
strcpy(scope_txt, STAT_SCOPE_PATTERN);
|
||||
memcpy(scope_txt + strlen(STAT_SCOPE_PATTERN), bo_ptr(req->buf) + si->applet.ctx.stats.scope_str, si->applet.ctx.stats.scope_len);
|
||||
scope_txt[strlen(STAT_SCOPE_PATTERN) + si->applet.ctx.stats.scope_len] = 0;
|
||||
}
|
||||
|
||||
|
||||
/* We don't want to land on the posted stats page because a refresh will
|
||||
* repost the data. We don't want this to happen on accident so we redirect
|
||||
@ -3003,14 +3013,17 @@ int http_handle_stats(struct session *s, struct channel *req)
|
||||
"Cache-Control: no-cache\r\n"
|
||||
"Content-Type: text/plain\r\n"
|
||||
"Connection: close\r\n"
|
||||
"Location: %s;st=%s\r\n"
|
||||
"Location: %s;st=%s%s%s%s\r\n"
|
||||
"\r\n",
|
||||
uri->uri_prefix,
|
||||
((si->applet.ctx.stats.st_code > STAT_STATUS_INIT) &&
|
||||
(si->applet.ctx.stats.st_code < STAT_STATUS_SIZE) &&
|
||||
stat_status_codes[si->applet.ctx.stats.st_code]) ?
|
||||
stat_status_codes[si->applet.ctx.stats.st_code] :
|
||||
stat_status_codes[STAT_STATUS_UNKN]);
|
||||
stat_status_codes[STAT_STATUS_UNKN],
|
||||
(si->applet.ctx.stats.flags & STAT_HIDE_DOWN) ? ";up" : "",
|
||||
(si->applet.ctx.stats.flags & STAT_NO_REFRESH) ? ";norefresh" : "",
|
||||
scope_txt);
|
||||
|
||||
s->txn.status = 303;
|
||||
s->logs.tv_request = now;
|
||||
@ -7827,6 +7840,44 @@ int stats_check_uri(struct stream_interface *si, struct http_txn *txn, struct pr
|
||||
}
|
||||
h++;
|
||||
}
|
||||
|
||||
si->applet.ctx.stats.scope_str = 0;
|
||||
si->applet.ctx.stats.scope_len = 0;
|
||||
h = uri + uri_auth->uri_len;
|
||||
while (h <= uri + msg->sl.rq.u_l - 8) {
|
||||
if (memcmp(h, STAT_SCOPE_INPUT_NAME "=", strlen(STAT_SCOPE_INPUT_NAME) + 1) == 0) {
|
||||
int itx = 0;
|
||||
const char *h2;
|
||||
char scope_txt[STAT_SCOPE_TXT_MAXLEN + 1];
|
||||
const char *err;
|
||||
|
||||
h += strlen(STAT_SCOPE_INPUT_NAME) + 1;
|
||||
h2 = h;
|
||||
si->applet.ctx.stats.scope_str = h2 - msg->chn->buf->p;
|
||||
while (*h != ';' && *h != '\0' && *h != '&' && *h != ' ' && *h != '\n') {
|
||||
itx++;
|
||||
h++;
|
||||
}
|
||||
|
||||
if (itx > STAT_SCOPE_TXT_MAXLEN)
|
||||
itx = STAT_SCOPE_TXT_MAXLEN;
|
||||
si->applet.ctx.stats.scope_len = itx;
|
||||
|
||||
/* scope_txt = search query, si->applet.ctx.stats.scope_len is always <= STAT_SCOPE_TXT_MAXLEN */
|
||||
memcpy(scope_txt, h2, itx);
|
||||
scope_txt[itx] = '\0';
|
||||
err = invalid_char(scope_txt);
|
||||
if (err) {
|
||||
/* bad char in search text => clear scope */
|
||||
si->applet.ctx.stats.scope_str = 0;
|
||||
si->applet.ctx.stats.scope_len = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
h++;
|
||||
}
|
||||
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -2081,6 +2081,55 @@ char *env_expand(char *in)
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
/* same as strstr() but case-insensitive and with limit length */
|
||||
const char *strnistr(const char *str1, int len_str1, const char *str2, int len_str2)
|
||||
{
|
||||
char *pptr, *sptr, *start;
|
||||
uint slen, plen;
|
||||
uint tmp1, tmp2;
|
||||
|
||||
if (str1 == NULL || len_str1 == 0) // search pattern into an empty string => search is not found
|
||||
return NULL;
|
||||
|
||||
if (str2 == NULL || len_str2 == 0) // pattern is empty => every str1 match
|
||||
return str1;
|
||||
|
||||
if (len_str1 < len_str2) // pattern is longer than string => search is not found
|
||||
return NULL;
|
||||
|
||||
for (tmp1 = 0, start = (char *)str1, pptr = (char *)str2, slen = len_str1, plen = len_str2; slen >= plen; start++, slen--) {
|
||||
while (toupper(*start) != toupper(*str2)) {
|
||||
start++;
|
||||
slen--;
|
||||
tmp1++;
|
||||
|
||||
if (tmp1 >= len_str1)
|
||||
return NULL;
|
||||
|
||||
/* if pattern longer than string */
|
||||
if (slen < plen)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
sptr = start;
|
||||
pptr = (char *)str2;
|
||||
|
||||
tmp2 = 0;
|
||||
while (toupper(*sptr) == toupper(*pptr)) {
|
||||
sptr++;
|
||||
pptr++;
|
||||
tmp2++;
|
||||
|
||||
if (*pptr == '\0' || tmp2 == len_str2) /* end of pattern found */
|
||||
return start;
|
||||
if (*sptr == '\0' || tmp2 == len_str1) /* end of string found and the pattern is not fully found */
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Local variables:
|
||||
* c-indent-level: 8
|
||||
|
Loading…
Reference in New Issue
Block a user