You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by mi...@apache.org on 2022/02/08 12:34:53 UTC

svn commit: r1897866 - in /httpd/httpd/trunk: changes-entries/ab-rampdelay.txt docs/manual/programs/ab.xml support/ab.c

Author: minfrin
Date: Tue Feb  8 12:34:52 2022
New Revision: 1897866

URL: http://svn.apache.org/viewvc?rev=1897866&view=rev
Log:
ab: Add an optional ramp delay when starting concurrent connections so
as to not trigger denial of service protection in the network. Report
levels of concurrency achieved in cases where the test completes before
full concurrency is achieved.

Added:
    httpd/httpd/trunk/changes-entries/ab-rampdelay.txt
Modified:
    httpd/httpd/trunk/docs/manual/programs/ab.xml
    httpd/httpd/trunk/support/ab.c

Added: httpd/httpd/trunk/changes-entries/ab-rampdelay.txt
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/changes-entries/ab-rampdelay.txt?rev=1897866&view=auto
==============================================================================
--- httpd/httpd/trunk/changes-entries/ab-rampdelay.txt (added)
+++ httpd/httpd/trunk/changes-entries/ab-rampdelay.txt Tue Feb  8 12:34:52 2022
@@ -0,0 +1,5 @@
+  *) ab: Add an optional ramp delay when starting concurrent connections so
+     as to not trigger denial of service protection in the network. Report
+     levels of concurrency achieved in cases where the test completes before
+     full concurrency is achieved. [Graham Leggett]
+

Modified: httpd/httpd/trunk/docs/manual/programs/ab.xml
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/docs/manual/programs/ab.xml?rev=1897866&r1=1897865&r2=1897866&view=diff
==============================================================================
--- httpd/httpd/trunk/docs/manual/programs/ab.xml (original)
+++ httpd/httpd/trunk/docs/manual/programs/ab.xml Tue Feb  8 12:34:52 2022
@@ -57,6 +57,7 @@
     [ -<strong>P</strong> <var>proxy-auth-username</var>:<var>password</var> ]
     [ -<strong>q</strong> ]
     [ -<strong>r</strong> ]
+    [ -<strong>R</strong> <var>rampdelay</var> ]
     [ -<strong>s</strong> <var>timeout</var> ]
     [ -<strong>S</strong> ]
     [ -<strong>t</strong> <var>timelimit</var> ]
@@ -170,6 +171,17 @@
     <dt><code>-r</code></dt>
     <dd>Don't exit on socket receive errors.</dd>
 
+    <dt><code>-R <var>rampdelay</var></code></dt>
+    <dd>Milliseconds in between each new connection when starting up.
+    Default is no delay. Starting too many concurrent connections at once can
+    trigger denial of service protection on the network, which limits the
+    effectiveness of the test. Introducing a delay between starting each
+    concurrent connection can work around this problem. The test may complete
+    before full ramp up of concurrent connections is achieved. If this happens,
+    the total number of concurrent connections achieved is noted in the results.
+    <br />
+    Available in 2.5.1 and later.</dd>
+
     <dt><code>-s <var>timeout</var></code></dt>
     <dd>Maximum number of seconds to wait before the socket times out.
     Default is 30 seconds.<br />

Modified: httpd/httpd/trunk/support/ab.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/support/ab.c?rev=1897866&r1=1897865&r2=1897866&view=diff
==============================================================================
--- httpd/httpd/trunk/support/ab.c (original)
+++ httpd/httpd/trunk/support/ab.c Tue Feb  8 12:34:52 2022
@@ -128,6 +128,7 @@
 #include "apr_strings.h"
 #include "apr_network_io.h"
 #include "apr_file_io.h"
+#include "apr_ring.h"
 #include "apr_time.h"
 #include "apr_getopt.h"
 #include "apr_general.h"
@@ -246,11 +247,16 @@ typedef enum {
 
 #define CBUFFSIZE (8192)
 
+typedef struct connection connection;
+
 struct connection {
+    APR_RING_ENTRY(connection) delay_list;
+    void (*delay_fn)(struct connection *);
     apr_pool_t *ctx;
     apr_socket_t *aprsock;
     apr_pollfd_t pollfd;
     int state;
+    apr_time_t delay;
     apr_size_t read;            /* amount of bytes read */
     apr_size_t bread;           /* amount of body read */
     apr_size_t rwrite, rwrote;  /* keep pointers in what we write - across
@@ -280,6 +286,10 @@ struct data {
     apr_interval_time_t time;     /* time for connection */
 };
 
+APR_RING_HEAD(delay_head_t, connection);
+
+struct delay_head_t delay_head;
+
 #define ap_min(a,b) (((a)<(b))?(a):(b))
 #define ap_max(a,b) (((a)>(b))?(a):(b))
 #define ap_round_ms(a) ((apr_time_t)((a) + 500)/1000)
@@ -296,6 +306,7 @@ int send_body = 0;      /* non-zero if s
 int requests = 1;       /* Number of requests to make */
 int heartbeatres = 100; /* How often do we say we're alive */
 int concurrency = 1;    /* Number of multiple requests to make */
+int concurrent = 0;     /* Number of multiple requests actually made */
 int percentile = 1;     /* Show percentile served */
 int nolength = 0;       /* Accept variable document length */
 int confidence = 1;     /* Show confidence estimator and warnings */
@@ -324,6 +335,7 @@ const char *fullurl;
 const char *colonhost;
 int isproxy = 0;
 apr_interval_time_t aprtimeout = apr_time_from_sec(30); /* timeout value */
+apr_interval_time_t ramp = apr_time_from_msec(0); /* ramp delay */
 
 /* overrides for ab-generated common headers */
 const char *opt_host;   /* which optional "Host:" header specified, if any */
@@ -399,6 +411,9 @@ apr_xlate_t *from_ascii, *to_ascii;
 static void write_request(struct connection * c);
 static void close_connection(struct connection * c);
 
+static void output_html_results(void);
+static void output_results(int sig);
+
 /* --------------------------------------------------------- */
 
 /* simple little function to write an error string and exit */
@@ -408,6 +423,12 @@ static void err(const char *s)
     fprintf(stderr, "%s\n", s);
     if (done)
         printf("Total of %d requests completed\n" , done);
+
+    if (use_html)
+        output_html_results();
+    else
+        output_results(0);
+
     exit(1);
 }
 
@@ -422,6 +443,12 @@ static void apr_err(const char *s, apr_s
         s, apr_strerror(rv, buf, sizeof buf), rv);
     if (done)
         printf("Total of %d requests completed\n" , done);
+
+    if (use_html)
+        output_html_results();
+    else
+        output_results(0);
+
     exit(rv);
 }
 
@@ -967,6 +994,8 @@ static void output_results(int sig)
         printf("Document Length:        %" APR_SIZE_T_FMT " bytes\n", doclen);
     printf("\n");
     printf("Concurrency Level:      %d\n", concurrency);
+    printf("Concurrency achieved:   %d\n", concurrent);
+    printf("Rampup delay:           %" APR_TIME_T_FMT " [ms]\n", apr_time_as_msec(ramp));
     printf("Time taken for tests:   %.3f seconds\n", timetaken);
     printf("Complete requests:      %d\n", done);
     printf("Failed requests:        %d\n", bad);
@@ -1247,6 +1276,12 @@ static void output_html_results(void)
     printf("<tr %s><th colspan=2 %s>Concurrency Level:</th>"
        "<td colspan=2 %s>%d</td></tr>\n",
        trstring, tdstring, tdstring, concurrency);
+    printf("<tr %s><th colspan=2 %s>Concurrency achieved:</th>"
+       "<td colspan=2 %s>%d</td></tr>\n",
+       trstring, tdstring, tdstring, concurrent);
+    printf("<tr %s><th colspan=2 %s>Rampup delay:</th>"
+       "<td colspan=2 %s>%" APR_TIME_T_FMT " [ms]</td></tr>\n",
+       trstring, tdstring, tdstring, apr_time_as_msec(ramp));
     printf("<tr %s><th colspan=2 %s>Time taken for tests:</th>"
        "<td colspan=2 %s>%.3f seconds</td></tr>\n",
        trstring, tdstring, tdstring, timetaken);
@@ -1362,16 +1397,24 @@ static void start_connect(struct connect
         return;
     }
 
+    c->delay = 0;
+    c->delay_fn = NULL;
+
     c->read = 0;
     c->bread = 0;
     c->keepalive = 0;
     c->cbx = 0;
     c->gotheader = 0;
     c->rwrite = 0;
-    if (c->ctx)
+    if (c->ctx) {
         apr_pool_clear(c->ctx);
-    else
+    }
+    else {
         apr_pool_create(&c->ctx, cntxt);
+        concurrent++;
+    }
+
+    APR_RING_ELEM_INIT((c), delay_list);
 
     if ((rv = apr_socket_create(&c->aprsock, destsa->family,
                 SOCK_STREAM, 0, c->ctx)) != APR_SUCCESS) {
@@ -1747,9 +1790,13 @@ read_more:
             /* We have received the header, so we know this destination socket
              * address is working, so initialize all remaining requests. */
             if (!requests_initialized) {
+                apr_time_t now = apr_time_now();
                 for (i = 1; i < concurrency; i++) {
                     con[i].socknum = i;
-                    start_connect(&con[i]);
+                    con[i].delay = now + (i * ramp);
+                    con[i].delay_fn = &start_connect;
+
+                    APR_RING_INSERT_TAIL(&delay_head, &con[i], connection, delay_list);
                 }
                 requests_initialized = 1;
             }
@@ -1988,14 +2035,36 @@ static void test(void)
     do {
         apr_int32_t n;
         const apr_pollfd_t *pollresults, *pollfd;
+        apr_interval_time_t t = aprtimeout;
+        apr_time_t now = apr_time_now();
+
+        while (!APR_RING_EMPTY(&delay_head, connection, delay_list)) {
+
+            struct connection *c = APR_RING_FIRST(&delay_head);
+
+            if (c->delay <= now) {
+                APR_RING_REMOVE(c, delay_list);
+                c->delay = 0;
+                c->delay_fn(c);
+            }
+            else {
+                t = c->delay - now;
+                break;
+            }
+        };
 
-        n = concurrency;
+        n = concurrent;
         do {
-            status = apr_pollset_poll(readbits, aprtimeout, &n, &pollresults);
+            status = apr_pollset_poll(readbits, t, &n, &pollresults);
         } while (APR_STATUS_IS_EINTR(status));
 
-        if (status != APR_SUCCESS)
+        if (APR_STATUS_IS_TIMEUP(status) &&
+                !APR_RING_EMPTY(&delay_head, connection, delay_list)) {
+            continue;
+        }
+        else if (status != APR_SUCCESS) {
             apr_err("apr_pollset_poll", status);
+        }
 
         for (i = 0, pollfd = pollresults; i < n; i++, pollfd++) {
             struct connection *c;
@@ -2160,6 +2229,8 @@ static void usage(const char *progname)
     fprintf(stderr, "                    This implies -n 50000\n");
     fprintf(stderr, "    -s timeout      Seconds to max. wait for each response\n");
     fprintf(stderr, "                    Default is 30 seconds\n");
+    fprintf(stderr, "    -R rampdelay    Milliseconds in between each new connection when starting up\n");
+    fprintf(stderr, "                    Default is no delay\n");
     fprintf(stderr, "    -b windowsize   Size of TCP send/receive buffer, in bytes\n");
     fprintf(stderr, "    -B address      Address to bind to when making outgoing connections\n");
     fprintf(stderr, "    -p postfile     File containing data to POST. Remember also to set -T\n");
@@ -2386,8 +2457,10 @@ int main(int argc, const char * const ar
 
     myhost = NULL; /* 0.0.0.0 or :: */
 
+    APR_RING_INIT(&delay_head, connection, delay_list);
+
     apr_getopt_init(&opt, cntxt, argc, argv);
-    while ((status = apr_getopt(opt, "n:c:t:s:b:T:p:u:v:lrkVhwiIx:y:z:C:H:P:A:g:X:de:SqB:m:"
+    while ((status = apr_getopt(opt, "n:c:t:s:b:T:p:u:v:lrkVhwiIx:y:z:C:H:P:A:g:X:de:SqB:m:R:"
 #ifdef USE_SSL
             "Z:f:E:"
 #endif
@@ -2431,6 +2504,9 @@ int main(int argc, const char * const ar
             case 's':
                 aprtimeout = apr_time_from_sec(atoi(opt_arg)); /* timeout value */
                 break;
+            case 'R':
+                ramp = apr_time_from_msec(atoi(opt_arg)); /* ramp delay */
+                break;
             case 'p':
                 if (method != NO_METH)
                     err("Cannot mix POST with other methods\n");