dgaudet 98/08/19 21:07:45
Modified: src CHANGES src/support ab.c Log: Add the ability to benchmark POST requests. Note I tweaked Kurt's submission to fit our style guide... and I corrected a few comments and crud that have been in ab.c since the zb.c days. And for some reason ab was printing the results twice for me, I fixed that. PR: 2871 Submitted by: Kurt Sussman <[EMAIL PROTECTED]> [modified by Dean] Revision Changes Path 1.1030 +3 -0 apache-1.3/src/CHANGES Index: CHANGES =================================================================== RCS file: /export/home/cvs/apache-1.3/src/CHANGES,v retrieving revision 1.1029 retrieving revision 1.1030 diff -u -r1.1029 -r1.1030 --- CHANGES 1998/08/17 18:36:16 1.1029 +++ CHANGES 1998/08/20 04:07:43 1.1030 @@ -1,5 +1,8 @@ Changes with Apache 1.3.2 + *) Add the ability to do POST requests to the ab benchmarking tool. + [Kurt Sussman <[EMAIL PROTECTED]>] PR#2871 + *) Bump up MAX_ENV_FLAGS in mod_rewrite.h from the too conservatice limit of 5 to 10 because there are some users out there who always have 5 to 8 variables in one RewriteRule and had to patch mod_rewrite.h for every 1.12 +162 -31 apache-1.3/src/support/ab.c Index: ab.c =================================================================== RCS file: /export/home/cvs/apache-1.3/src/support/ab.c,v retrieving revision 1.11 retrieving revision 1.12 diff -u -r1.11 -r1.12 --- ab.c 1998/07/29 10:14:40 1.11 +++ ab.c 1998/08/20 04:07:45 1.12 @@ -79,20 +79,39 @@ ** Michael Campanella <[EMAIL PROTECTED]> ** - Enhanced by Dean Gaudet <[EMAIL PROTECTED]>, November 1997 ** - Cleaned up by Ralf S. Engelschall <[EMAIL PROTECTED]>, March 1998 +** - POST and verbosity by Kurt Sussman <[EMAIL PROTECTED]>, August 1998 ** */ -#define VERSION "1.1" +#define VERSION "1.2" /* -------------------------------------------------------------------- */ /* affects include files on Solaris */ #define BSD_COMP +/* allow compilation outside an Apache build tree */ +#ifdef NO_APACHE_INCLUDES +#include <sys/time.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <fcntl.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> +#include <errno.h> +#include <sys/ioctl.h> +#include <string.h> + +#define ap_select select +#else /* (!)NO_APACHE_INCLUDES */ #include "ap_config.h" #include <fcntl.h> #include <sys/time.h> - +#endif /* NO_APACHE_INCLUDES */ /* ------------------- DEFINITIONS -------------------------- */ /* maximum number of requests on a time limited test */ @@ -129,6 +148,8 @@ /* --------------------- GLOBALS ---------------------------- */ +int verbosity = 0; /* no verbosity by default */ +int posting = 0; /* GET by default */ int requests = 1; /* Number of requests to make */ int concurrency = 1; /* Number of multiple requests to make */ int tlimit = 0; /* time limit in cs */ @@ -136,17 +157,23 @@ char servername[1024]; /* name that server reports */ char hostname[1024]; /* host name */ char path[1024]; /* path name */ +char postfile[1024]; /* name of file containing post data */ +char* postdata; /* *buffer containing data from postfile */ +int postlen = 0; /* length of data to be POSTed */ +char content_type[1024]; /* content type to put in POST header */ int port = 80; /* port number */ int doclen = 0; /* the length the document should be */ int totalread = 0; /* total number of bytes read */ int totalbread = 0; /* totoal amount of entity body read */ +int totalposted = 0; /* total number of bytes posted, inc. headers */ int done = 0; /* number of requests we have done */ int doneka = 0; /* number of keep alive connections done */ int good = 0, bad = 0; /* number of good and bad requests */ /* store error cases */ int err_length = 0, err_conn = 0, err_except = 0; +int err_response = 0; struct timeval start, endtime; @@ -155,7 +182,7 @@ int reqlen; /* one global throw-away buffer to read stuff into */ -char buffer[4096]; +char buffer[8192]; struct connection *con; /* connection array */ struct data *stats; /* date for each request */ @@ -169,7 +196,12 @@ static void err(char *s) { - perror(s); + if (errno) { + perror(s); + } + else { + printf("%s", s); + } exit(errno); } @@ -181,7 +213,13 @@ static void write_request(struct connection *c) { gettimeofday(&c->connect, 0); + /* XXX: this could use writev for posting -- more efficient -djg */ write(c->fd, request, reqlen); + if (posting) { + write(c->fd,postdata, postlen); + totalposted += (reqlen + postlen); + } + c->state = STATE_READ; FD_SET(c->fd, &readbits); FD_CLR(c->fd, &writebits); @@ -214,7 +252,7 @@ /* --------------------------------------------------------- */ -/* calculate and output results and exit */ +/* calculate and output results */ static void output_results(void) { @@ -239,16 +277,26 @@ if (bad) printf(" (Connect: %d, Length: %d, Exceptions: %d)\n", err_conn, err_length, err_except); + if (err_response) + printf("Non-2xx responses: %d\n", err_response); if (keepalive) printf("Keep-Alive requests: %d\n", doneka); printf("Total transferred: %d bytes\n", totalread); + if (posting) + printf("Total POSTed: %d\n", totalposted); printf("HTML transferred: %d bytes\n", totalbread); /* avoid divide by zero */ if (timetaken) { printf("Requests per second: %.2f\n", 1000 * (float) (done) / timetaken); - printf("Transfer rate: %.2f kb/s\n", + printf("Transfer rate: %.2f kb/s received\n", (float) (totalread) / timetaken); + if (posting) { + printf(" %.2f kb/s sent\n", + (float)(totalposted)/timetaken); + printf(" %.2f kb/s total\n", + (float)(totalread + totalposted)/timetaken); + } } { @@ -268,12 +316,13 @@ total += s.time; } printf("\nConnnection Times (ms)\n"); - printf(" min avg max\n"); - printf("Connect: %5d %5d %5d\n", mincon, totalcon / requests, maxcon); - printf("Total: %5d %5d %5d\n", mintot, total / requests, maxtot); + printf(" min avg max\n"); + printf("Connect: %5d %5d %5d\n", mincon, totalcon / requests, maxcon); + printf("Processing: %5d %5d %5d\n", + mintot - mincon, (total/requests) - (totalcon/requests), + maxtot - maxcon); + printf("Total: %5d %5d %5d\n", mintot, total / requests, maxtot); } - - exit(0); } /* --------------------------------------------------------- */ @@ -305,8 +354,7 @@ close(c->fd); err_conn++; if (bad++ > 10) { - printf("\nTest aborted after 10 failures\n\n"); - exit(1); + err("\nTest aborted after 10 failures\n\n"); } start_connect(c); } @@ -323,7 +371,7 @@ static void close_connection(struct connection *c) { if (c->read == 0 && c->keepalive) { - /* server has legitiamately shut down an idle keep alive request */ + /* server has legitimately shut down an idle keep alive request */ good--; /* connection never happend */ } else { @@ -363,6 +411,8 @@ static void read_connection(struct connection *c) { int r; + char *part; + char respcode[4]; /* 3 digits and null */ r = read(c->fd, buffer, sizeof(buffer)); if (r == 0 || (r < 0 && errno != EAGAIN)) { @@ -390,6 +440,9 @@ c->cbx += tocopy; space -= tocopy; c->cbuff[c->cbx] = 0; /* terminate for benefit of strstr */ + if (verbosity >= 4) { + printf("LOG: header received:\n%s\n", c->cbuff); + } s = strstr(c->cbuff, "\r\n\r\n"); /* this next line is so that we talk to NCSA 1.5 which blatantly breaks the http specifaction */ @@ -406,8 +459,7 @@ /* header is in invalid or too big - close connection */ close(c->fd); if (bad++ > 10) { - printf("\nTest aborted after 10 failures\n\n"); - exit(1); + err("\nTest aborted after 10 failures\n\n"); } FD_CLR(c->fd, &writebits); start_connect(c); @@ -428,6 +480,23 @@ *q = 0; } + /* FIXME: 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 test against. -djg */ + + /* check response code */ + part = strstr(c->cbuff, "HTTP"); /* really HTTP/1.x_ */ + strncpy(respcode, (part+strlen("HTTP/1.x_")), 3); + respcode[3] = '\0'; + if (respcode[0] != '2') { + err_response++; + if (verbosity >= 2) printf ("WARNING: Response code not 2xx (%s)\n", respcode); + } + else if (verbosity >= 3) { + printf("LOG: Response code = %s\n", respcode); + } + c->gotheader = 1; *s = 0; /* terminate at end of header */ if (keepalive && @@ -435,8 +504,7 @@ || strstr(c->cbuff, "keep-alive"))) { /* for benefit of MSIIS */ char *cl; cl = strstr(c->cbuff, "Content-Length:"); - /* for cacky servers like NCSA which break the spec and send a - lower case 'l' */ + /* handle NCSA, which sends Content-length: */ if (!cl) cl = strstr(c->cbuff, "Content-length:"); if (cl) { @@ -503,7 +571,7 @@ struct hostent *he; he = gethostbyname(hostname); if (!he) - err("gethostbyname"); + err("bad hostname"); server.sin_family = he->h_addrtype; server.sin_port = htons(port); server.sin_addr.s_addr = ((unsigned long *) (he->h_addr_list[0]))[0]; @@ -518,7 +586,8 @@ FD_ZERO(&writebits); /* setup request */ - sprintf(request, "GET %s HTTP/1.0\r\n" + if (!posting) { + sprintf(request, "GET %s HTTP/1.0\r\n" "User-Agent: ApacheBench/%s\r\n" "%s" "Host: %s\r\n" @@ -528,6 +597,24 @@ VERSION, keepalive ? "Connection: Keep-Alive\r\n" : "", hostname); + } + else { + sprintf(request, "POST %s HTTP/1.0\r\n" + "User-Agent: ApacheBench/%s\r\n" + "%s" + "Host: %s\r\n" + "Accept: */*\r\n" + "Content-length: %d\r\n" + "Content-type: %s\r\n" + "\r\n", + path, + VERSION, + keepalive ? "Connection: Keep-Alive\r\n" : "", + hostname, postlen, + (content_type) ? content_type : "text/plain"); + } + + if (verbosity >= 2) printf("INFO: POST header == \n---\n%s\n---\n", request); reqlen = strlen(request); @@ -553,7 +640,6 @@ gettimeofday(&now, 0); if (tlimit && timedif(now, start) > (tlimit * 1000)) { requests = done; /* so stats are correct */ - output_results(); } /* Timeout of 30 seconds. */ @@ -561,8 +647,7 @@ timeout.tv_usec = 0; n = ap_select(FD_SETSIZE, &sel_read, &sel_write, &sel_except, &timeout); if (!n) { - printf("\nServer timed out\n\n"); - exit(1); + err("\nServer timed out\n\n"); } if (n < 1) err("select"); @@ -580,9 +665,8 @@ if (FD_ISSET(s, &sel_write)) write_request(&con[i]); } - if (done >= requests) - output_results(); } + output_results(); } /* ------------------------------------------------------- */ @@ -604,8 +688,11 @@ fprintf(stderr, " -n requests Number of requests to perform\n"); fprintf(stderr, " -c concurrency Number of multiple requests to make\n"); fprintf(stderr, " -t timelimit Seconds to max. wait for responses\n"); + fprintf(stderr, " -p postfile File containg data to POST\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, " -V Print version number and exit\n"); fprintf(stderr, " -k Use HTTP KeepAlive feature\n"); - fprintf(stderr, " -v Display version and copyright information\n"); fprintf(stderr, " -h Display usage information (this message)\n"); exit(EINVAL); } @@ -640,21 +727,50 @@ /* ------------------------------------------------------- */ +/* read data to POST from file, save contents and length */ + +int open_postfile(char *pfile) +{ + int postfd, status; + struct stat postfilestat; + + if ((postfd = open(pfile, O_RDONLY)) == -1) { + printf("Invalid postfile name (%s)\n", pfile); + return errno; + } + if ((status = fstat(postfd, &postfilestat)) == -1) { + perror("Can\'t stat postfile\n"); + return status; + } + postdata = malloc(postfilestat.st_size); + if (!postdata) { + printf("Can\'t alloc postfile buffer\n"); + return ENOMEM; + } + if (read(postfd, postdata, postfilestat.st_size) != postfilestat.st_size) { + printf("error reading postfilen"); + return EIO; + } + postlen = postfilestat.st_size; + return 0; +} + +/* ------------------------------------------------------- */ + extern char *optarg; extern int optind, opterr, optopt; /* sort out command-line args and call test */ int main(int argc, char **argv) { - int c; + int c, r; optind = 1; - while ((c = getopt(argc, argv, "n:c:t:kvh")) > 0) { + while ((c = getopt(argc, argv, "n:c:t:T:p:v:kVh")) > 0) { switch (c) { case 'n': requests = atoi(optarg); if (!requests) { - printf("Invalid number of requests\n"); - exit(1); + err("Invalid number of requests\n"); } break; case 'k': @@ -663,11 +779,25 @@ case 'c': concurrency = atoi(optarg); break; + case 'p': + if (0 == (r = open_postfile(optarg))) { + posting = 1; + } + else if (postdata) { + exit(r); + } + break; + case 'v': + verbosity = atoi(optarg); + break; case 't': tlimit = atoi(optarg); requests = MAX_REQUESTS; /* need to size data array on something */ break; - case 'v': + case 'T': + strcpy(content_type, optarg); + break; + case 'V': copyright(); exit(0); break; @@ -692,6 +822,7 @@ copyright(); test(); + exit(0); }