diff options
-rw-r--r-- | changes-entries/ab-rampdelay.txt | 5 | ||||
-rw-r--r-- | docs/manual/programs/ab.xml | 12 | ||||
-rw-r--r-- | support/ab.c | 90 |
3 files changed, 100 insertions, 7 deletions
diff --git a/changes-entries/ab-rampdelay.txt b/changes-entries/ab-rampdelay.txt new file mode 100644 index 0000000000..866030cc76 --- /dev/null +++ b/changes-entries/ab-rampdelay.txt @@ -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] + diff --git a/docs/manual/programs/ab.xml b/docs/manual/programs/ab.xml index abdb26016d..4c32c397a0 100644 --- a/docs/manual/programs/ab.xml +++ b/docs/manual/programs/ab.xml @@ -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 /> diff --git a/support/ab.c b/support/ab.c index 20de3a844c..e9580ab587 100644 --- a/support/ab.c +++ b/support/ab.c @@ -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 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"); |