ab.c revision bd685a7a27da8536c5da7b4b8b4cb9c3010bc876
* See the License for the specific language governing permissions and * limitations under the License. ** This program is based on ZeusBench V1.0 written by Adam Twiss ** This software is provided "as is" and any express or implied waranties, ** including but not limited to, the implied warranties of merchantability and ** fitness for a particular purpose are disclaimed. In no event shall ** Zeus Technology Ltd. be liable for any direct, indirect, incidental, special, ** exemplary, or consequential damaged (including, but not limited to, ** procurement of substitute good or services; loss of use, data, or profits; ** or business interruption) however caused and on theory of liability. Whether ** in contract, strict liability or tort (including negligence or otherwise) ** arising in any way out of the use of this software, even if advised of the ** possibility of such damage. ** - Originally written by Adam Twiss <adam@zeus.co.uk>, March 1996 ** with input from Mike Belshe <mbelshe@netscape.com> and ** Michael Campanella <campanella@stevms.enet.dec.com> ** - Enhanced by Dean Gaudet <dgaudet@apache.org>, November 1997 ** - Cleaned up by Ralf S. Engelschall <rse@apache.org>, March 1998 ** - POST and verbosity by Kurt Sussman <kls@merlot.com>, August 1998 ** - HTML table output added by David N. Welton <davidw@prosa.it>, January 1999 ** - Added Cookie, Arbitrary header and auth support. <dirkx@webweaving.org>, April 1999 ** - Increased version number - as some of the socket/error handling has ** fundamentally changed - and will give fundamentally different results ** in situations where a server is dropping requests. Therefore you can ** no longer compare results of AB as easily. Hence the inc of the version. ** They should be closer to the truth though. Sander & <dirkx@covalent.net>, End 2000. ** - Fixed proxy functionality, added median/mean statistics, added gnuplot ** confidence guestimators and warnings. Sander & <dirkx@covalent.net>, End 2000 ** - Fixed serious int overflow issues which would cause realistic (longer ** than a few minutes) run's to have wrong (but believable) results. Added ** trapping of connection errors which influenced measurements. ** Contributed by Sander Temme, Early 2001 ** - Changed timeout behavour during write to work whilst the sockets ** are filling up and apr_write() does writes a few - but not all. ** This will potentially change results. <dirkx@webweaving.org>, April 2001 ** Improvements to concurrent processing: ** - Enabled non-blocking connect()s. ** - Prevent blocking calls to apr_socket_recv() (thereby allowing AB to ** manage its entire set of socket descriptors). ** - Any error returned from apr_socket_recv() that is not EAGAIN or EOF ** is now treated as fatal. ** Contributed by Aaron Bannert, April 24, 2002 ** Internalized the version string - this string is part ** of the Agent: header and the result output. ** Adopted SSL code by Madhu Mathihalli <madhusudan_mathihalli@hp.com> ** [PATCH] ab with SSL support Posted Wed, 15 Aug 2001 20:55:06 GMT ** Introduces four 'if (int == value)' tests per non-ssl request. ** Switched to the new abstract pollset API, allowing ab to ** take advantage of future apr_pollset_t scalability improvements. ** Contributed by Brian Pane, August 31, 2002 ** SIGINT now triggers output_results(). ** Contributed by colm, March 30, 2006 /* Note: this version string should start with \d+[\d\.]* and be a valid * string for an HTTP Agent: header when prefixed with 'ApacheBench/'. * It should reflect the version of AB - and not that of the apache server * it happens to accompany. And it should be updated or changed whenever * the results are no longer fundamentally comparable to the results of * a previous version of ab. Either due to a change in the logic of * ab - or to due to a change in the distribution it is compiled with * (such as an APR change in for example blocking). * - has various other poor buffer attacks related to the lazy parsing of * response headers from the server * - doesn't implement much of HTTP/1.x, only accepts certain forms of * - (performance problem) heavy use of strstr shows up top in profile * only an issue for loopback usage /* -------------------------------------------------------------------- */ /* Hmmm... This source code isn't being compiled in ASCII. * In order for data that flows over the network to make * sense, we need to translate to/from ASCII. /* affects include files on Solaris */ /* Libraries for RSA SSL-C */ /* Libraries on most systems.. */ /* ------------------- DEFINITIONS -------------------------- */ /* maximum number of requests on a time limited test */ /* good old state hostname */ int cbx;
/* offset in cbuffer */ int keepalive;
/* non-zero if a keep-alive request */ int gotheader;
/* non-zero if we have the entire header in connect,
/* Connected, start writing */ done;
/* Connection closed */ int read;
/* number of bytes read */ #
define ap_min(a,b) ((a)<(b))?(a):(b)
#
define ap_max(a,b) ((a)>(b))?(a):(b)
/* --------------------- GLOBALS ---------------------------- */ int verbosity = 0;
/* no verbosity by default */ int posting = 0;
/* GET by default */ 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 confidence =
1;
/* Show confidence estimator and warnings */ int tlimit = 0;
/* time limit in secs */ int keepalive = 0;
/* try and do keepalive connections */ int windowsize = 0;
/* we use the OS default window size */ char servername[
1024];
/* name that server reports */ char *
hostname;
/* host name from URL */ char *
host_field;
/* value of "Host:" header field */ char *
path;
/* path name */ char postfile[
1024];
/* name of file containing post data */ char *
postdata;
/* *buffer containing data from postfile */ char content_type[
1024];
/* content type to put in POST header */ char *
cookie,
/* optional cookie line */ *
hdrs;
/* optional arbitrary headers */char *
csvperc;
/* CSV Percentile file */ * XXX - this is now a per read/write transact type of value int use_html = 0;
/* use html in the report */ long started = 0;
/* number of requests started, so no excess */ long totalread = 0;
/* total number of bytes read */ long totalbread = 0;
/* totoal amount of entity body read */ long totalposted = 0;
/* total number of bytes posted, inc. headers */ long done = 0;
/* number of requests we have done */ long doneka = 0;
/* number of keep alive connections done */ long good = 0,
bad = 0;
/* number of good and bad requests */ long epipe = 0;
/* number of broken pipe writes */ /* global request (and its length) */ /* one global throw-away buffer to read stuff into */ /* interesting percentiles */ int percs[] = {
50,
66,
75,
80,
90,
95,
98,
99,
100};
struct data *
stats;
/* date for each request */ /* --------------------------------------------------------- */ /* simple little function to write an error string and exit */ printf(
"Total of %ld requests completed\n" ,
done);
/* simple little function to write an APR error string and exit */ printf(
"Total of %ld requests completed\n" ,
done);
/* --------------------------------------------------------- */ /* write out request to a connection - assumes we can write * (small) request out in one go into our new socket buffer * seed in the current time (usually just 4 bytes) * seed in the current process id (usually just 4 bytes) * seed in some current state of the run-time stack (128 bytes) /* dump the extension list too */ for (i=
1; i<
count; i++) {
printf(
"Send request timed out!\n");
* Bail early on the most common case * Let's hope this traps EWOULDBLOCK too ! printf(
"Send request failed!\n");
/* --------------------------------------------------------- */ /* calculate and output results */ /* If ab was interrupted, we are only interested in requests printf(
"Time taken for tests: %ld.%03ld seconds\n",
printf(
" (Connect: %d, Length: %d, Exceptions: %d)\n",
/* avoid divide by zero */ printf(
"Requests per second: %.2f [#/sec] (mean)\n",
printf(
"Time per request: %.3f [ms] (mean)\n",
printf(
"Time per request: %.3f [ms] (mean, across all concurrent requests)\n",
/* work out connection times */ /* calculating the sample variance: the sum of the squared deviations, divided by n-1 */ perror(
"Cannot open gnuplot output file");
fprintf(
out,
"starttime\tseconds\tctime\tdtime\tttime\twait\n");
* XXX: what is better; this hideous cast of the compradre function; or * the four warnings during compile ? dirkx just does not know and (
int (*) (
const void *,
const void *))
compradre);
(
int (*) (
const void *,
const void *))
compri);
(
int (*) (
const void *,
const void *))
compwait);
(
int (*) (
const void *,
const void *))
comprando);
printf(
"\nConnection Times (ms)\n");
printf(
" min mean[+/-sd] median max\n");
printf(
"ERROR: The median and mean for " what " are more than twice the standard\n" \
" deviation apart. These results are NOT reliable.\n"); \
printf(
"WARNING: The median and mean for " what " are not within a normal deviation\n" \
" These results are probably not that reliable.\n"); \
/* Sorted on total connect times */ printf(
"\nPercentage of the requests served within a certain time (ms)\n");
for (i = 0; i <
sizeof(
percs) /
sizeof(
int); i++) {
else if (
percs[i] >=
100)
perror(
"Cannot open CSV output file");
fprintf(
out,
"" "Percentage served" "," "Time in ms" "\n");
for (i = 0; i <
100; i++) {
/* --------------------------------------------------------- */ /* calculate and output results in HTML */ printf(
"<tr %s><th colspan=2 %s>Server Software:</th>" "<td colspan=2 %s>%s</td></tr>\n",
printf(
"<tr %s><th colspan=2 %s>Server Hostname:</th>" "<td colspan=2 %s>%s</td></tr>\n",
printf(
"<tr %s><th colspan=2 %s>Server Port:</th>" "<td colspan=2 %s>%hd</td></tr>\n",
printf(
"<tr %s><th colspan=2 %s>Document Path:</th>" "<td colspan=2 %s>%s</td></tr>\n",
printf(
"<tr %s><th colspan=2 %s>Document Length:</th>" printf(
"<tr %s><th colspan=2 %s>Concurrency Level:</th>" "<td colspan=2 %s>%d</td></tr>\n",
printf(
"<tr %s><th colspan=2 %s>Time taken for tests:</th>" printf(
"<tr %s><th colspan=2 %s>Complete requests:</th>" "<td colspan=2 %s>%ld</td></tr>\n",
printf(
"<tr %s><th colspan=2 %s>Failed requests:</th>" "<td colspan=2 %s>%ld</td></tr>\n",
printf(
"<tr %s><td colspan=4 %s > (Connect: %d, Length: %d, Exceptions: %d)</td></tr>\n",
printf(
"<tr %s><th colspan=2 %s>Non-2xx responses:</th>" "<td colspan=2 %s>%d</td></tr>\n",
printf(
"<tr %s><th colspan=2 %s>Keep-Alive requests:</th>" "<td colspan=2 %s>%ld</td></tr>\n",
printf(
"<tr %s><th colspan=2 %s>Total transferred:</th>" "<td colspan=2 %s>%ld bytes</td></tr>\n",
printf(
"<tr %s><th colspan=2 %s>Total POSTed:</th>" "<td colspan=2 %s>%ld</td></tr>\n",
printf(
"<tr %s><th colspan=2 %s>HTML transferred:</th>" "<td colspan=2 %s>%ld bytes</td></tr>\n",
/* avoid divide by zero */ printf(
"<tr %s><th colspan=2 %s>Requests per second:</th>" "<td colspan=2 %s>%.2f</td></tr>\n",
printf(
"<tr %s><th colspan=2 %s>Transfer rate:</th>" "<td colspan=2 %s>%.2f kb/s received</td></tr>\n",
printf(
"<tr %s><td colspan=2 %s> </td>" "<td colspan=2 %s>%.2f kb/s sent</td></tr>\n",
printf(
"<tr %s><td colspan=2 %s> </td>" "<td colspan=2 %s>%.2f kb/s total</td></tr>\n",
/* work out connection times */ if (
requests > 0) {
/* avoid division by zero (if 0 requests) */ printf(
"<tr %s><th %s colspan=4>Connnection Times (ms)</th></tr>\n",
printf(
"<tr %s><th %s> </th> <th %s>min</th> <th %s>avg</th> <th %s>max</th></tr>\n",
printf(
"<tr %s><th %s>Connect:</th>" printf(
"<tr %s><th %s>Processing:</th>" printf(
"<tr %s><th %s>Total:</th>" /* --------------------------------------------------------- */ /* start asnchronous non-blocking connection */ "\nTest aborted after 10 failures\n\n");
/* connected first time */ /* --------------------------------------------------------- */ /* close down connection and save stats */ * server has legitimately shut down an idle keep alive request good--;
/* connection never happened */ /* --------------------------------------------------------- */ /* read data from connection */ char respcode[
4];
/* 3 digits and null */ /* connection closed cleanly: */ /* catch legitimate fatal apr_socket_recv errors */ err_except++;
/* XXX: is this the right error counter? */ /* XXX: Should errors here be fatal, or should we allow a * certain number of them before completely failing? -aaron */ fprintf(
stderr,
"only simple translation is supported (%d/%u/%u)\n",
c->
cbuff[c->
cbx] = 0;
/* terminate for benefit of strstr */ * this next line is so that we talk to NCSA 1.5 which blatantly * breaks the http specifaction /* read rest next time */ /* header is in invalid or too big - close connection */ err(
"\nTest aborted after 10 failures\n\n");
* this is first time, extract some interesting info * XXX: this parsing isn't even remotely HTTP compliant... but in * the interest of speed it doesn't totally have to be, it just * needs to be extended to handle whatever servers folks want to /* check response code */ *s = 0;
/* terminate at end of header */ ||
strstr(c->
cbuff,
"keep-alive"))) {
/* for benefit of MSIIS */ /* handle NCSA, which sends Content-length: */ /* The response may not have a Content-Length header */ /* outside header, everything we have read is entity body */ /* finished a keep-alive connection */ /* --------------------------------------------------------- */ "User-Agent: ApacheBench/%s\r\n" keepalive ?
"Connection: Keep-Alive\r\n" :
"",
"User-Agent: ApacheBench/%s\r\n" keepalive ?
"Connection: Keep-Alive\r\n" :
"",
err(
"Request too long\n");
* Combine headers and (optional) post file into one contineous buffer fprintf(
stderr,
"error creating request buffer: out of memory\n");
fprintf(
stderr,
"only simple translation is supported (%d/%u/%u)\n",
/* This only needs to be done once */ /* Output the results if the user terminates the run early. */ /* initialise lots of requests */ /* check for time limit expiry */ break;
/* no need to do another round */ err(
"\nServer timed out\n\n");
for (i = 0; i < n; i++) {
* If the connection isn't connected how can we check it? * Notes: APR_POLLHUP is set after FIN is received on some * systems, so treat that like APR_POLLIN so that we try to read * Some systems return APR_POLLERR with APR_POLLHUP. We need to * call read_connection() for APR_POLLHUP, so check for * APR_POLLHUP first so that a closed connection isn't treated * like an I/O error. If it is, we never figure out that the * connection is done and we loop here endlessly calling "\nTest aborted after 10 failures\n\n");
* When using a select based poll every time we check the bits * are reset. In 1.3's ab we copied the FD_SET's each time * through, but here we're going to check the state and if the * connection is in STATE_READ or STATE_CONNECTING we'll add the * socket back in as APR_POLLIN. /* ------------------------------------------------------- */ /* display copyright information */ /* display usage information */ "://]hostname[:port]/path\n",
progname);
fprintf(
stderr,
" -c concurrency Number of multiple requests to make\n");
fprintf(
stderr,
" -t timelimit Seconds to max. wait for responses\n");
fprintf(
stderr,
" -T content-type Content-type header for POSTing\n");
fprintf(
stderr,
" -v verbosity How much troubleshooting info to print\n");
fprintf(
stderr,
" -x attributes String to insert as table attributes\n");
fprintf(
stderr,
" -y attributes String to insert as tr attributes\n");
fprintf(
stderr,
" -z attributes String to insert as td or th attributes\n");
fprintf(
stderr,
" -C attribute Add cookie, eg. 'Apache=1234. (repeatable)\n");
fprintf(
stderr,
" -H attribute Add Arbitrary header line, eg. 'Accept-Encoding: gzip'\n");
fprintf(
stderr,
" Inserted after all normal header lines. (repeatable)\n");
fprintf(
stderr,
" -A attribute Add Basic WWW Authentication, the attributes\n");
fprintf(
stderr,
" are a colon separated username and password.\n");
fprintf(
stderr,
" -P attribute Add Basic Proxy Authentication, the attributes\n");
fprintf(
stderr,
" are a colon separated username and password.\n");
fprintf(
stderr,
" -X proxy:port Proxyserver and port number to use\n");
fprintf(
stderr,
" -S Do not show confidence estimators and warnings.\n");
fprintf(
stderr,
" -g filename Output collected data to gnuplot format file.\n");
fprintf(
stderr,
" -e filename Output CSV file with percentages served\n");
fprintf(
stderr,
" -h Display usage information (this message)\n");
/* ------------------------------------------------------- */ /* split URL into parts */ /* Save a copy for the proxy */ if (*
url ==
'[') {
/* IPv6 numeric address string */ if (
port == 0) {
/* no port specified */ /* ------------------------------------------------------- */ /* read data to POST from file, save contents and length */ /* ------------------------------------------------------- */ /* sort out command-line args and call test */ err(
"Invalid number of requests\n");
err(
"Cannot mix POST and HEAD\n");
err(
"Cannot mix POST and HEAD\n");
* assume username passwd already to be in colon separated form. * Ready to be uu-encoded. err(
"Authentication credentials too long\n");
* assume username passwd already to be in colon separated form. err(
"Proxy credentials too long\n");
* if any of the following three are used, turn on html output * assume proxy-name[:port] "total number of requests\n",
argv[0]);
heartbeatres =
100;
/* but never more often than once every 100 * have been closed at the other end. */