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