5 This program is Copyright (C) Zeus Technology Limited 1996.
7 This program may be used and copied freely providing this copyright notice
10 This software is provided "as is" and any express or implied waranties,
11 including but not limited to, the implied warranties of merchantability and
12 fitness for a particular purpose are disclaimed. In no event shall
13 Zeus Technology Ltd. be liable for any direct, indirect, incidental, special,
14 exemplary, or consequential damaged (including, but not limited to,
15 procurement of substitute good or services; loss of use, data, or profits;
16 or business interruption) however caused and on theory of liability. Whether
17 in contract, strict liability or tort (including negligence or otherwise)
18 arising in any way out of the use of this software, even if advised of the
19 possibility of such damage.
21 Written by Adam Twiss (adam@zeus.co.uk). March 1996
23 Thanks to the following people for their input:
24 Mike Belshe (mbelshe@netscape.com)
25 Michael Campanella (campanella@stevms.enet.dec.com)
29 /* -------------------- Notes on compiling ------------------------------
31 This should compile unmodified using gcc on HP-UX, FreeBSD, Linux,
32 IRIX, Solaris, AIX and Digital Unix (OSF). On Solaris 2.x you will
33 need to compile with "-lnsl -lsocket" options. If you have any
34 difficulties compiling then let me know.
36 On SunOS 4.x.x you may need to compile with -DSUNOS4 to add the following
37 two lines of code which appear not to exist in my SunOS headers */
41 extern int optind, opterr, optopt;
44 /* -------------------------------------------------------------------- */
46 /* affects include files on Solaris */
50 #include <sys/ioctl.h>
55 #include <sys/socket.h>
56 #include <netinet/in.h>
59 #include <sys/ioctl.h>
62 /* ------------------- DEFINITIONS -------------------------- */
64 /* maximum number of requests on a time limited test */
65 #define MAX_REQUESTS 50000
67 /* good old state machine */
68 #define STATE_UNCONNECTED 0
69 #define STATE_CONNECTING 1
78 int read; /* amount of bytes read */
79 int bread; /* amount of body read */
80 int length; /* Content-Length value used for keep-alive */
81 char cbuff[CBUFFSIZE]; /* a buffer to store server response header */
82 int cbx; /* offset in cbuffer */
83 int keepalive; /* non-zero if a keep-alive request */
84 int gotheader; /* non-zero if we have the entire header in cbuff */
85 struct timeval start, connect, done;
90 int read; /* number of bytes read */
91 int ctime; /* time in ms to connect */
92 int time; /* time in ms for connection */
95 #define min(a,b) ((a)<(b))?(a):(b)
96 #define max(a,b) ((a)>(b))?(a):(b)
98 /* --------------------- GLOBALS ---------------------------- */
100 int requests = 1; /* Number of requests to make */
101 int concurrency = 1; /* Number of multiple requests to make */
102 int tlimit = 0; /* time limit in cs */
103 int keepalive = 0; /* try and do keepalive connections */
104 char *machine; /* Machine name */
105 char *file; /* file name to use */
106 char server_name[80]; /* name that server reports */
107 int port = 80; /* port to use */
109 int doclen = 0; /* the length the document should be */
110 int totalread = 0; /* total number of bytes read */
111 int totalbread = 0; /* totoal amount of entity body read */
112 int done=0; /* number of requests we have done */
113 int doneka=0; /* number of keep alive connections done */
114 int good=0, bad=0; /* number of good and bad requests */
116 /* store error cases */
117 int err_length = 0, err_conn = 0, err_except = 0;
119 struct timeval start, endtime;
121 /* global request (and its length) */
125 /* one global throw-away buffer to read stuff into */
128 struct connection *con; /* connection array */
129 struct data *stats; /* date for each request */
131 fd_set readbits, writebits; /* bits for select */
132 struct sockaddr_in server; /* server addr structure */
134 /* --------------------------------------------------------- */
136 /* simple little function to perror and exit */
138 static void err(char *s)
144 /* --------------------------------------------------------- */
146 /* write out request to a connection - assumes we can write
147 (small) request out in one go into our new socket buffer */
149 void write_request(struct connection *c)
151 gettimeofday(&c->connect,0);
152 write(c->fd,request, reqlen);
153 c->state = STATE_READ;
154 FD_SET(c->fd, &readbits);
155 FD_CLR(c->fd, &writebits);
158 /* --------------------------------------------------------- */
160 /* make an fd non blocking */
162 void nonblock(int fd)
165 ioctl(fd, FIONBIO, &i);
168 /* --------------------------------------------------------- */
170 /* returns the time in ms between two timevals */
172 int timedif(struct timeval a, struct timeval b)
176 us = a.tv_usec - b.tv_usec;
178 s = a.tv_sec - b.tv_sec;
183 /* --------------------------------------------------------- */
185 /* calculate and output results and exit */
187 void output_results()
191 gettimeofday(&endtime,0);
192 timetaken = timedif(endtime, start);
195 printf("Server: %s\n", server_name);
196 printf("Document Length: %d\n", doclen);
197 printf("Concurency Level: %d\n", concurrency);
198 printf("Time taken for tests: %d.%03d seconds\n",
199 timetaken/1000, timetaken%1000);
200 printf("Complete requests: %d\n", done);
201 printf("Failed requests: %d\n", bad);
202 if(bad) printf(" (Connect: %d, Length: %d, Exceptions: %d)\n",
203 err_conn, err_length, err_except);
204 if(keepalive) printf("Keep-Alive requests: %d\n", doneka);
205 printf("Bytes transfered: %d\n", totalread);
206 printf("HTML transfered: %d\n", totalbread);
208 /* avoid divide by zero */
210 printf("Requests per seconds: %.2f\n", 1000*(float)(done)/timetaken);
211 printf("Transfer rate: %.2f kb/s\n",
212 (float)(totalread)/timetaken);
216 /* work out connection times */
218 int totalcon=0, total=0;
219 int mincon=9999999, mintot=999999;
220 int maxcon=0, maxtot=0;
222 for(i=0; i<requests; i++) {
223 struct data s = stats[i];
224 mincon = min(mincon, s.ctime);
225 mintot = min(mintot, s.time);
226 maxcon = max(maxcon, s.ctime);
227 maxtot = max(maxtot, s.time);
231 printf("\nConnnection Times (ms)\n");
232 printf(" min avg max\n");
233 printf("Connect: %5d %5d %5d\n",mincon, totalcon/requests, maxcon );
234 printf("Total: %5d %5d %5d\n", mintot, total/requests, maxtot);
241 /* --------------------------------------------------------- */
243 /* start asnchronous non-blocking connection */
245 void start_connect(struct connection *c)
253 c->fd = socket(AF_INET, SOCK_STREAM, 0);
254 if(c->fd<0) err("socket");
257 gettimeofday(&c->start,0);
259 if(connect(c->fd, (struct sockaddr *) &server, sizeof(server))<0) {
260 if(errno==EINPROGRESS) {
261 c->state = STATE_CONNECTING;
262 FD_SET(c->fd, &writebits);
269 printf("\nTest aborted after 10 failures\n\n");
276 /* connected first time */
280 /* --------------------------------------------------------- */
282 /* close down connection and save stats */
284 void close_connection(struct connection *c)
286 if(c->read == 0 && c->keepalive) {
287 /* server has legitiamately shut down an idle keep alive request */
288 good--; /* connection never happend */
292 /* first time here */
294 } else if (c->bread!=doclen) {
300 if(done < requests) {
302 gettimeofday(&c->done,0);
304 s.ctime = timedif(c->connect, c->start);
305 s.time = timedif(c->done, c->start);
311 FD_CLR(c->fd, &readbits);
312 FD_CLR(c->fd, &writebits);
319 /* --------------------------------------------------------- */
321 /* read data from connection */
323 void read_connection(struct connection *c)
327 r=read(c->fd,buffer,sizeof(buffer));
328 if(r==0 || (r<0 && errno!=EAGAIN)) {
334 if(r<0 && errno==EAGAIN) return;
342 int space = CBUFFSIZE - c->cbx - 1; /* -1 to allow for 0 terminator */
343 int tocopy = (space<r)?space:r;
344 memcpy(c->cbuff+c->cbx, buffer, space);
345 c->cbx += tocopy; space -= tocopy;
346 c->cbuff[c->cbx] = 0; /* terminate for benefit of strstr */
347 s = strstr(c->cbuff, "\r\n\r\n");
348 /* this next line is so that we talk to NCSA 1.5 which blatantly breaks
349 the http specifaction */
350 if(!s) { s = strstr(c->cbuff,"\n\n"); l=2; }
353 /* read rest next time */
357 /* header is in invalid or too big - close connection */
360 printf("\nTest aborted after 10 failures\n\n");
363 FD_CLR(c->fd, &writebits);
368 /* have full header */
370 /* this is first time, extract some interesting info */
372 p = strstr(c->cbuff, "Server:");
374 if(p) { p+=8; while(*p>32) *q++ = *p++; }
379 *s = 0; /* terminate at end of header */
381 (strstr(c->cbuff, "Keep-Alive")
382 || strstr(c->cbuff, "keep-alive"))) /* for benefit of MSIIS */
385 cl = strstr(c->cbuff, "Content-Length:");
386 /* for cacky servers like NCSA which break the spec and send a
388 if(!cl) cl = strstr(c->cbuff, "Content-length:");
391 c->length = atoi(cl+16);
394 c->bread += c->cbx - (s+l-c->cbuff) + r-tocopy;
395 totalbread += c->bread;
399 /* outside header, everything we have read is entity body */
404 if(c->keepalive && (c->bread >= c->length)) {
405 /* finished a keep-alive connection */
409 /* first time here */
411 } else if(c->bread!=doclen) { bad++; err_length++; }
412 if(done < requests) {
414 gettimeofday(&c->done,0);
416 s.ctime = timedif(c->connect, c->start);
417 s.time = timedif(c->done, c->start);
420 c->keepalive = 0; c->length = 0; c->gotheader=0; c->cbx = 0;
421 c->read = c->bread = 0;
423 c->start = c->connect; /* zero connect time with keep-alive */
427 /* --------------------------------------------------------- */
433 struct timeval timeout, now;
434 fd_set sel_read, sel_except, sel_write;
438 /* get server information */
440 he = gethostbyname(machine);
441 if (!he) err("gethostbyname");
442 server.sin_family = he->h_addrtype;
443 server.sin_port = htons(port);
444 server.sin_addr.s_addr = ((unsigned long *)(he->h_addr_list[0]))[0];
447 con = malloc(concurrency*sizeof(struct connection));
448 memset(con,0,concurrency*sizeof(struct connection));
450 stats = malloc(requests * sizeof(struct data));
456 sprintf(request,"GET %s HTTP/1.0\r\nUser-Agent: ZeusBench/1.0\r\n"
457 "%sHost: %s\r\nAccept: */*\r\n\r\n", file,
458 keepalive?"Connection: Keep-Alive\r\n":"", machine );
460 reqlen = strlen(request);
462 /* ok - lets start */
463 gettimeofday(&start,0);
465 /* initialise lots of requests */
466 for(i=0; i<concurrency; i++) start_connect(&con[i]);
468 while(done<requests) {
470 /* setup bit arrays */
471 memcpy(&sel_except, &readbits, sizeof(readbits));
472 memcpy(&sel_read, &readbits, sizeof(readbits));
473 memcpy(&sel_write, &writebits, sizeof(readbits));
475 /* check for time limit expiry */
476 gettimeofday(&now,0);
477 if(tlimit && timedif(now,start) > (tlimit*1000)) {
478 requests=done; /* so stats are correct */
482 /* Timeout of 30 seconds. */
483 timeout.tv_sec=30; timeout.tv_usec=0;
484 n=select(256, &sel_read, &sel_write, &sel_except, &timeout);
486 printf("\nServer timed out\n\n");
489 if(n<1) err("select");
491 for(i=0; i<concurrency; i++) {
493 if(FD_ISSET(s, &sel_except)) {
496 start_connect(&con[i]);
499 if(FD_ISSET(s, &sel_read)) read_connection(&con[i]);
500 if(FD_ISSET(s, &sel_write)) write_request(&con[i]);
502 if(done>=requests) output_results();
507 /* ------------------------------------------------------- */
509 /* display usage information */
511 void usage(char *progname) {
512 printf("\nZeusBench v1.0\n\n");
513 printf("Usage: %s <machine> <file> [-k] [-n requests | -t timelimit (sec)]"
514 "\n\t\t[-c concurrency] [-p port] \n",progname);
515 printf("Filename should start with a '/' e.g. /index.html\n\n");
519 /* ------------------------------------------------------- */
521 /* sort out command-line args and call test */
523 int main(int argc, char **argv) {
525 if (argc < 3) usage(argv[0]);
530 while ((c = getopt(argc,argv,"p:n:c:d:t:d:k"))>0) {
535 requests = atoi(optarg);
537 printf("Invalid number of requests\n");
545 concurrency = atoi(optarg);
551 tlimit = atoi(optarg);
552 requests = MAX_REQUESTS; /* need to size data array on something */