diff --git a/include/common/standard.h b/include/common/standard.h
index 4de4f7343..df061b25a 100644
--- a/include/common/standard.h
+++ b/include/common/standard.h
@@ -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 */
diff --git a/include/proto/dumpstats.h b/include/proto/dumpstats.h
index 0674c323a..9b4196173 100644
--- a/include/proto/dumpstats.h
+++ b/include/proto/dumpstats.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 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);
diff --git a/include/types/stream_interface.h b/include/types/stream_interface.h
index a80849139..25008943c 100644
--- a/include/types/stream_interface.h
+++ b/include/types/stream_interface.h
@@ -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 */
diff --git a/src/dumpstats.c b/src/dumpstats.c
index 578247e99..2069048e7 100644
--- a/src/dumpstats.c
+++ b/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,
- "
",
- px->uuid);
+ "Choose the action to perform on the checked servers : "
+ ""
+ ""
+ " "
+ "",
+ px->uuid);
}
chunk_appendf(&trash, "\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,
+ "
\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,
- "Show all servers
\n",
+ "Show all servers
\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,
- "Hide 'DOWN' servers
\n",
+ "Hide 'DOWN' servers
\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,
- "Enable refresh
\n",
+ "Enable refresh
\n",
uri->uri_prefix,
(si->applet.ctx.stats.flags & STAT_HIDE_DOWN) ? ";up" : "",
- "");
+ "",
+ scope_txt);
else
chunk_appendf(&trash,
- "Disable refresh
\n",
+ "Disable refresh
\n",
uri->uri_prefix,
(si->applet.ctx.stats.flags & STAT_HIDE_DOWN) ? ";up" : "",
- ";norefresh");
+ ";norefresh",
+ scope_txt);
}
chunk_appendf(&trash,
- "Refresh now
\n",
+ "Refresh now
\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,
- "CSV export
\n",
+ "CSV export
\n",
uri->uri_prefix,
- (uri->refresh > 0) ? ";norefresh" : "");
+ (uri->refresh > 0) ? ";norefresh" : "",
+ scope_txt);
chunk_appendf(&trash,
""
diff --git a/src/proto_http.c b/src/proto_http.c
index 206701ec4..610d5a57f 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -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;
}
diff --git a/src/standard.c b/src/standard.c
index adeb335ff..cd60a94ed 100644
--- a/src/standard.c
+++ b/src/standard.c
@@ -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