bottleneck testcase based on rubbos
[bottlenecks.git] / rubbos / app / httpd-2.0.64 / test / zb.c
1
2 /*                          ZeusBench V1.01
3                             ===============
4
5 This program is Copyright (C) Zeus Technology Limited 1996.
6
7 This program may be used and copied freely providing this copyright notice
8 is not removed.
9
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.
20
21      Written by Adam Twiss (adam@zeus.co.uk).  March 1996
22
23 Thanks to the following people for their input:
24   Mike Belshe (mbelshe@netscape.com) 
25   Michael Campanella (campanella@stevms.enet.dec.com)
26
27 */
28
29 /* -------------------- Notes on compiling ------------------------------
30
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.
35
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 */
38
39 #ifdef SUNOS4
40 extern char *optarg; 
41 extern int optind, opterr, optopt;   
42 #endif
43
44 /*  -------------------------------------------------------------------- */
45
46 /* affects include files on Solaris */
47 #define BSD_COMP
48
49 #include <sys/time.h> 
50 #include <sys/ioctl.h>
51 #include <unistd.h>
52 #include <stdlib.h>
53 #include <stdio.h>
54 #include <fcntl.h>
55 #include <sys/socket.h>
56 #include <netinet/in.h>
57 #include <netdb.h> 
58 #include <errno.h>
59 #include <sys/ioctl.h>
60 #include <string.h>
61
62 /* ------------------- DEFINITIONS -------------------------- */
63
64 /* maximum number of requests on a time limited test */
65 #define MAX_REQUESTS 50000 
66
67 /* good old state machine */
68 #define STATE_UNCONNECTED 0
69 #define STATE_CONNECTING  1
70 #define STATE_READ        2
71
72 #define CBUFFSIZE       512
73
74 struct connection 
75 {
76   int fd;
77   int state;
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; 
86 };
87
88 struct data 
89 {
90   int read;      /* number of bytes read */
91   int ctime;     /* time in ms to connect */
92   int time;      /* time in ms for connection */
93 };
94
95 #define min(a,b) ((a)<(b))?(a):(b)
96 #define max(a,b) ((a)>(b))?(a):(b)
97
98 /* --------------------- GLOBALS ---------------------------- */
99
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 */
108
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 */
115
116 /* store error cases */
117 int err_length = 0, err_conn = 0, err_except = 0;
118
119 struct timeval start, endtime;
120
121 /* global request (and its length) */
122 char request[512];
123 int reqlen;
124
125 /* one global throw-away buffer to read stuff into */
126 char buffer[4096];
127
128 struct connection *con;      /* connection array */
129 struct data *stats;          /* date for each request */
130
131 fd_set readbits, writebits;  /* bits for select */
132 struct sockaddr_in server;   /* server addr structure */
133
134 /* --------------------------------------------------------- */
135
136 /* simple little function to perror and exit */
137
138 static void err(char *s) 
139 {
140   perror(s);
141   exit(errno);
142 }
143
144 /* --------------------------------------------------------- */
145
146 /* write out request to a connection - assumes we can write 
147    (small) request out in one go into our new socket buffer  */
148
149 void write_request(struct connection *c)
150 {
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);
156 }
157
158 /* --------------------------------------------------------- */
159
160 /* make an fd non blocking */
161
162 void nonblock(int fd) 
163 {
164   int i=1;
165   ioctl(fd, FIONBIO, &i);
166 }
167
168 /* --------------------------------------------------------- */
169
170 /* returns the time in ms between two timevals */
171
172 int timedif(struct timeval a, struct timeval b)
173 {
174   register int us,s;
175
176   us = a.tv_usec - b.tv_usec;
177   us /= 1000;
178   s = a.tv_sec - b.tv_sec;
179   s *= 1000;
180   return s+us;
181 }
182
183 /* --------------------------------------------------------- */
184
185 /* calculate and output results and exit */
186
187 void output_results()
188 {
189   int timetaken;
190   
191   gettimeofday(&endtime,0);
192   timetaken = timedif(endtime, start);
193   
194   printf("\n---\n");
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);
207   
208   /* avoid divide by zero */
209   if(timetaken) {
210     printf("Requests per seconds:   %.2f\n", 1000*(float)(done)/timetaken);
211     printf("Transfer rate:          %.2f kb/s\n", 
212            (float)(totalread)/timetaken);
213   }
214
215   {
216     /* work out connection times */
217     int i;
218     int totalcon=0, total=0;
219     int mincon=9999999, mintot=999999;
220     int maxcon=0, maxtot=0;
221
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);
228       totalcon += s.ctime;
229       total += s.time;
230     }
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);
235     printf("---\n\n");
236   }
237
238   exit(0);
239 }
240
241 /* --------------------------------------------------------- */
242
243 /* start asnchronous non-blocking connection */
244
245 void start_connect(struct connection *c)
246 {
247   c->read = 0;
248   c->bread = 0;
249   c->keepalive = 0;
250   c->cbx = 0; 
251   c->gotheader = 0;
252
253   c->fd = socket(AF_INET, SOCK_STREAM, 0);
254   if(c->fd<0) err("socket");
255
256   nonblock(c->fd);
257   gettimeofday(&c->start,0);
258
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);
263       return;
264     }
265     else {
266       close(c->fd);
267       err_conn++;
268       if(bad++>10) {
269         printf("\nTest aborted after 10 failures\n\n");
270         exit(1);
271       } 
272       start_connect(c);
273     }      
274   }
275   
276   /* connected first time */
277   write_request(c);
278 }
279
280 /* --------------------------------------------------------- */
281
282 /* close down connection and save stats */
283
284 void close_connection(struct connection *c)
285 {
286   if(c->read == 0 && c->keepalive) {
287     /* server has legitiamately shut down an idle keep alive request */
288     good--;  /* connection never happend */
289   } 
290   else {
291     if(good==1) {
292       /* first time here */
293       doclen = c->bread;
294     } else if (c->bread!=doclen) { 
295       bad++; 
296       err_length++; 
297     }
298     
299     /* save out time */
300     if(done < requests) {
301       struct data s;
302       gettimeofday(&c->done,0);
303       s.read = c->read;
304       s.ctime = timedif(c->connect, c->start);
305       s.time = timedif(c->done, c->start);
306       stats[done++] = s;
307     }
308   }
309
310   close(c->fd);
311   FD_CLR(c->fd, &readbits);
312   FD_CLR(c->fd, &writebits);
313
314   /* connect again */
315   start_connect(c); 
316   return;
317 }
318
319 /* --------------------------------------------------------- */
320
321 /* read data from connection */
322
323 void read_connection(struct connection *c)
324 {
325   int r;
326   
327   r=read(c->fd,buffer,sizeof(buffer));
328   if(r==0 || (r<0 && errno!=EAGAIN)) {
329     good++;
330     close_connection(c);
331     return;
332   } 
333   
334   if(r<0 && errno==EAGAIN) return;
335
336   c->read += r;
337   totalread += r;
338   
339   if(!c->gotheader) {
340     char *s;
341     int l=4;
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; }
351
352     if(!s) {
353        /* read rest next time */
354       if(space) 
355         return;
356       else {
357         /* header is in invalid or too big - close connection */
358         close(c->fd);
359         if(bad++>10) {
360           printf("\nTest aborted after 10 failures\n\n");
361           exit(1);
362         } 
363         FD_CLR(c->fd, &writebits);
364         start_connect(c);
365       } 
366     }
367     else {
368       /* have full header */
369       if(!good) {
370         /* this is first time, extract some interesting info */
371         char *p, *q;
372         p = strstr(c->cbuff, "Server:");
373         q = server_name;
374         if(p) { p+=8; while(*p>32) *q++ = *p++; }
375         *q = 0;
376       }
377         
378       c->gotheader = 1;
379       *s = 0; /* terminate at end of header */
380       if(keepalive && 
381          (strstr(c->cbuff, "Keep-Alive") 
382           || strstr(c->cbuff, "keep-alive")))  /* for benefit of MSIIS */
383         {
384         char *cl;
385         cl = strstr(c->cbuff, "Content-Length:");
386         /* for cacky servers like NCSA which break the spec and send a 
387            lower case 'l' */
388         if(!cl) cl = strstr(c->cbuff, "Content-length:");
389         if(cl) {
390           c->keepalive=1;
391           c->length = atoi(cl+16);
392         }
393       }
394       c->bread += c->cbx - (s+l-c->cbuff) + r-tocopy;
395       totalbread += c->bread;
396     }
397   }  
398   else {
399     /* outside header, everything we have read is entity body */
400     c->bread += r;
401     totalbread += r;
402   }
403
404   if(c->keepalive && (c->bread >= c->length)) {
405     /* finished a keep-alive connection */
406     good++; doneka++;
407     /* save out time */
408     if(good==1) {
409       /* first time here */
410       doclen = c->bread;
411     } else if(c->bread!=doclen) { bad++; err_length++; }
412     if(done < requests) {
413       struct data s;
414       gettimeofday(&c->done,0);
415       s.read = c->read;
416       s.ctime = timedif(c->connect, c->start);
417       s.time = timedif(c->done, c->start);
418       stats[done++] = s;
419     }
420     c->keepalive = 0; c->length = 0; c->gotheader=0; c->cbx = 0;
421     c->read = c->bread = 0;
422     write_request(c);
423     c->start = c->connect; /* zero connect time with keep-alive */
424   }
425 }
426
427 /* --------------------------------------------------------- */
428
429 /* run the tests */
430
431 int test() 
432 {
433   struct timeval timeout, now;
434   fd_set sel_read, sel_except, sel_write;
435   int i;
436   
437   {
438     /* get server information */
439     struct hostent *he;
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];
445   }
446
447   con = malloc(concurrency*sizeof(struct connection));
448   memset(con,0,concurrency*sizeof(struct connection));
449   
450   stats = malloc(requests * sizeof(struct data));
451
452   FD_ZERO(&readbits);
453   FD_ZERO(&writebits);
454
455   /* setup request */
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 );
459     
460   reqlen = strlen(request);
461
462   /* ok - lets start */
463   gettimeofday(&start,0);
464
465   /* initialise lots of requests */
466   for(i=0; i<concurrency; i++) start_connect(&con[i]);
467
468   while(done<requests) {
469     int n;
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));
474
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 */
479       output_results();
480     }
481
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);
485     if(!n) {
486       printf("\nServer timed out\n\n");
487       exit(1);
488     }
489     if(n<1) err("select");
490
491     for(i=0; i<concurrency; i++) {
492       int s = con[i].fd;
493       if(FD_ISSET(s, &sel_except)) {
494         bad++; 
495         err_except++;
496         start_connect(&con[i]);
497         continue;
498       }
499       if(FD_ISSET(s, &sel_read)) read_connection(&con[i]);
500       if(FD_ISSET(s, &sel_write)) write_request(&con[i]);
501     }
502     if(done>=requests) output_results();
503   }
504   return 0;
505 }
506
507 /* ------------------------------------------------------- */
508
509 /* display usage information */
510
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");
516   exit(EINVAL);
517 }
518
519 /* ------------------------------------------------------- */
520
521 /* sort out command-line args and call test */
522
523 int main(int argc, char **argv) {
524   int c;
525   if (argc < 3) usage(argv[0]);
526   
527   machine = argv[1]; 
528   file = argv[2];
529   optind = 3;
530   while ((c = getopt(argc,argv,"p:n:c:d:t:d:k"))>0) {
531     switch(c) {
532     case 'd':
533       break;
534     case 'n': 
535       requests = atoi(optarg);
536       if(!requests) {
537         printf("Invalid number of requests\n");
538         exit(1);
539       }
540       break;
541     case 'k':
542       keepalive=1;
543       break;
544     case 'c':
545       concurrency = atoi(optarg);
546       break;
547     case 'p':
548       port = atoi(optarg);
549       break;
550     case 't':
551       tlimit = atoi(optarg);
552       requests = MAX_REQUESTS;  /* need to size data array on something */
553       break;
554     default:
555       usage(argv[0]);
556       break;
557     }
558   }   
559   test();
560   return 0;
561 }
562
563
564
565
566
567