summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--changes-entries/ab-rampdelay.txt5
-rw-r--r--docs/manual/programs/ab.xml12
-rw-r--r--support/ab.c90
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");