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 transferred: %d\n", totalread);
206
printf("HTML transferred: %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 */