1
2/*                          ZeusBench V1.01
3                            ===============
4
5This program is Copyright (C) Zeus Technology Limited 1996.
6
7This program may be used and copied freely providing this copyright notice
8is not removed.
9
10This software is provided "as is" and any express or implied waranties,
11including but not limited to, the implied warranties of merchantability and
12fitness for a particular purpose are disclaimed.  In no event shall
13Zeus Technology Ltd. be liable for any direct, indirect, incidental, special,
14exemplary, or consequential damaged (including, but not limited to,
15procurement of substitute good or services; loss of use, data, or profits;
16or business interruption) however caused and on theory of liability.  Whether
17in contract, strict liability or tort (including negligence or otherwise)
18arising in any way out of the use of this software, even if advised of the
19possibility of such damage.
20
21     Written by Adam Twiss (adam@zeus.co.uk).  March 1996
22
23Thanks 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
31This should compile unmodified using gcc on HP-UX, FreeBSD, Linux,
32IRIX, Solaris, AIX and Digital Unix (OSF).  On Solaris 2.x you will
33need to compile with "-lnsl -lsocket" options.  If you have any
34difficulties compiling then let me know.
35
36On SunOS 4.x.x you may need to compile with -DSUNOS4 to add the following
37two lines of code which appear not to exist in my SunOS headers */
38
39#ifdef SUNOS4
40extern char *optarg;
41extern 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
74struct 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
88struct 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
100int requests = 1;      /* Number of requests to make */
101int concurrency = 1;   /* Number of multiple requests to make */
102int tlimit = 0;        /* time limit in cs */
103int keepalive = 0;     /* try and do keepalive connections */
104char *machine;         /* Machine name */
105char *file;            /* file name to use */
106char server_name[80];  /* name that server reports */
107int port = 80;         /* port to use */
108
109int doclen = 0;        /* the length the document should be */
110int totalread = 0;     /* total number of bytes read */
111int totalbread = 0;    /* totoal amount of entity body read */
112int done=0;            /* number of requests we have done */
113int doneka=0;          /* number of keep alive connections done */
114int good=0, bad=0;     /* number of good and bad requests */
115
116/* store error cases */
117int err_length = 0, err_conn = 0, err_except = 0;
118
119struct timeval start, endtime;
120
121/* global request (and its length) */
122char request[512];
123int reqlen;
124
125/* one global throw-away buffer to read stuff into */
126char buffer[4096];
127
128struct connection *con;      /* connection array */
129struct data *stats;          /* date for each request */
130
131fd_set readbits, writebits;  /* bits for select */
132struct sockaddr_in server;   /* server addr structure */
133
134/* --------------------------------------------------------- */
135
136/* simple little function to perror and exit */
137
138static 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
149void 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
162void 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
172int 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
187void 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 transferred:      %d\n", totalread);
206  printf("HTML transferred:       %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
245void 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
284void 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
323void 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
431int 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
511void 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
523int 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
568