]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: stats: add proxy name filtering on the statistic page
authorde Lafond Guillaume <gdelafond@aquaray.com>
Mon, 15 Apr 2013 17:27:10 +0000 (19:27 +0200)
committerWilly Tarreau <w@1wt.eu>
Mon, 15 Apr 2013 20:50:33 +0000 (22:50 +0200)
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.

include/common/standard.h
include/proto/dumpstats.h
include/types/stream_interface.h
src/dumpstats.c
src/proto_http.c
src/standard.c

index 4de4f73433c2277bc67cf8a7581a474e648dd70b..df061b25a1f6f1837cee45fee9691a043829b486 100644 (file)
@@ -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 */
index 0674c323a7e60e1f4df80a51caf14c834951eb0e..9b41961734f3e9510337712a1a0b58599a97acff 100644 (file)
 #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);
index a8084913982cb258b6375a16ed7a6d1a164dbc28..25008943c8d2bc64884e697279a2136f9aa0cc38 100644 (file)
@@ -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 */
index 578247e99283e8feb9b2fd61a9f656890459bc72..2069048e781f6576069b7e3d4d858c4357e164a9 100644 (file)
@@ -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\">"
-                             "&nbsp;<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\">"
+                             "&nbsp;<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>"
index 206701ec4aa9a454f92efc27896bb08c74e25694..610d5a57fa1efdd47d26901f6c06e68a4b554591 100644 (file)
@@ -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;
 }
 
index adeb335fff89eb49e88cc43ec92e951c524dabee..cd60a94ed1fdb74721b436f141fb8b3259e2f41d 100644 (file)
@@ -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