]> git.ipfire.org Git - thirdparty/apache/httpd.git/commitdiff
ab: Add an optional ramp delay when starting concurrent connections so
authorGraham Leggett <minfrin@apache.org>
Tue, 8 Feb 2022 12:34:52 +0000 (12:34 +0000)
committerGraham Leggett <minfrin@apache.org>
Tue, 8 Feb 2022 12:34:52 +0000 (12:34 +0000)
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.

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1897866 13f79535-47bb-0310-9956-ffa450edef68

changes-entries/ab-rampdelay.txt [new file with mode: 0644]
docs/manual/programs/ab.xml
support/ab.c

diff --git a/changes-entries/ab-rampdelay.txt b/changes-entries/ab-rampdelay.txt
new file mode 100644 (file)
index 0000000..866030c
--- /dev/null
@@ -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]
+
index abdb26016d26a0861793b34f2b084765daa4a951..4c32c397a0af3f32ad8f7c4ec691937d892f8ef5 100644 (file)
@@ -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> ]
     <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 />
index 20de3a844ce4f0addfb490803e7e18a3e85d3ab5..e9580ab58782e85bb69f127e3189ed83f47b0b64 100644 (file)
 #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 sending body with request */
 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_status_t rv)
         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 connection * c)
         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 argv[])
 
     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 argv[])
             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");