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, - "
", - uri->uri_prefix); + "", + 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 : " - "" - "" - " " - "
", - 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, + "

  • Scope :
    \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