/************************************************************************** $Header$ * * * Copyright (C) 1995 Silicon Graphics, Inc. * * * * These coded instructions, statements, and computer programs were * * developed by SGI for public use. If any changes are made to this code* * please try to get the changes back to the author. Feel free to make * * modifications and changes to the code and release it. * * * **************************************************************************/ #include #include #include #include #include #include #ifndef WIN32 #include #endif /* WIN32 */ #include #ifndef WIN32 #include #include #include #include #include #include #include #else #define FD_SETSIZE 1024 /* max size for select() - keep before * and same size as MAXCLIENTS */ #include #include #include #include #endif /* WIN32 */ #include "sysdep.h" #include "bench.h" /* command line options/data */ int savefile = 0; int debug = 0; int norexec = 0; int haveproxyserver = 0; char proxyserver[MAXHOSTNAMELEN]; char network_mask_str[30] = "255.255.255.0"; unsigned network_mask = 0; int servaddrin_config = 0; int dumpall = 0; int testtime = 0; int havewebserver = 0; int numloops = 0; NETPORT portnum = 0; int redirect = 0; int record_all_transactions = 0; int uil_filelist_f = 0; int verbose = 0; char webserver[MAXHOSTNAMELEN]; char configfile[MAXPATHLEN]; char uil_filelist[NCCARGS]; char filelist[256][MAXPATHLEN]; fd_set zerofdset; /* other key data */ long int number_of_pages = 0; int totalnumclients = 0; int num_rexecs = 0; SOCKET socknum[MAXCLIENTS]; SOCKET sockIO[MAXTOTALPROCS]; SOCKET sockErr[MAXTOTALPROCS]; THREAD FILE *debugfile = stderr; struct hostent *master_phe; /* IP addresses for webmaster */ struct timeval sumedh_start, sumedh_end; void HostEntCpy(struct hostent *dest, struct hostent *src); static void usage(const char *progname) { fprintf(stderr, "Usage: %s [-a] [-d] -f config_file [-l numloops]\n", progname); fprintf(stderr, " [-p port_num] [-r] [-s] [-t run_time] \n"); fprintf(stderr, "\n"); fprintf(stderr, "-w webserver URL [URL ...]\n\n"); fprintf(stderr, "-a print timing information for all clients\n"); fprintf(stderr, "-d turn on debug statements\n"); fprintf(stderr, "-f config_file\tfile specifying clients\n"); fprintf(stderr, "-l number of iterations to retrieve uils\n"); fprintf(stderr, "-p port number of web server if not 80\n"); fprintf(stderr, "-r redirect stdout of clients to /tmp/webstone.xxx\n"); fprintf(stderr, "-s save client gets to /tmp/webstone.data.*\n"); fprintf(stderr, "-t run_time\tduration of test in minutes\n"); fprintf(stderr, "-w webserver\tname of webserver host to contact\n"); fprintf(stderr, "-u URL file\tfilelist of URLs\n"); fprintf(stderr, "-v verbose mode\n"); fprintf(stderr, "-P servername\tuse proxy server for transactions\n"); fprintf(stderr, "-W webserver addresses are in the config file\n"); fprintf(stderr, "-R record all transactions\n"); errexit("\n"); } static SOCKET passivesock(const NETPORT portnum, const char *protocol, const int qlen) { struct protoent *ppe; /* pointer to protocol info entry */ struct sockaddr_in sin; /* Internet endpoint address */ SOCKET s; /* socket descriptor */ int type; /* socket type */ D_PRINTF( "Beginning passivesock with errno %d\n",errno ); D_PRINTF( "Zeroing address structure\n" ); memset((char *)&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = INADDR_ANY; /* NOT USED: Map service name to portnumber */ D_PRINTF( "Mapping portnum errno %d\n",errno ); sin.sin_port = htons(portnum); /* Map protocol name to number */ D_PRINTF( "Mapping protocol name errno %d\n",errno ); if ((ppe = getprotobyname(protocol)) == 0) { errexit("Can't get \"%s\" protocol entry\n", protocol); } errno = 0; /* use protocol to choose socket type */ D_PRINTF( "Changing socket type, errno %d\n",errno ); if (strcmp(protocol, "udp") == 0) { type = SOCK_DGRAM; D_PRINTF( "Choosing SOCK_DGRAM\n" ); } else { type = SOCK_STREAM; D_PRINTF( "Choosing SOCK_STREAM, errno %d\n",errno ); } /* allocate a socket */ s = socket(PF_INET, type, ppe->p_proto); if (BADSOCKET(s)) { D_PRINTF( "Socket PF_INET %d %d returned %d with %s\n", type, ppe->p_proto, s, neterrstr() ); errexit("Can't create socket: %s\n", neterrstr()); } D_PRINTF( "Socket %d created with errno %d\n",s,errno ); /* Bind the socket */ if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) { errexit("Can't bind to port %d: %s\n", portnum, neterrstr()); } D_PRINTF( "Bind succeeded\n" ); /* If it's a stream, listen for connections */ /* NOTE: ON NT, the listen() backlog parm is silently limited to 5 conns */ if ((type == SOCK_STREAM) && BADSOCKET(listen(s, qlen))) { errexit("Can't listen on port %s: %s\n", portnum, neterrstr()); } D_PRINTF( "Listen succeeded\n" ); /* all done, return socket descriptor */ return(s); } /* abort clients -- called by SIGINT handler */ static void abort_clients(void) { /* Not supposed to have fprintf in a signal handler, but... */ fprintf(stdout, "Webmaster received SIGINT. Terminating.\n"); /* exit will close all open connections */ exit(2); } /* signal handler for SIGINT */ static void sig_int(int sig) { abort_clients(); } #ifdef WIN32 /* echo stdout/stderr from clients */ void echo_client(void *stream) { SOCKET *sockarr; FILE *outfile; int which_stream = (int) stream; char buf[BUFSIZ]; int i, len, rv; fd_set readfds; /* This code which handles the timeout may need to be ifdef'ed for WIN32 */ struct timeval timeout; timeout.tv_sec = (long)5; timeout.tv_usec = (long)0; if (which_stream) { sockarr = sockIO; outfile = stdout; } else { sockarr = sockErr; outfile = stderr; } D_PRINTF( "echo_client running\n" ); signal( SIGINT, SIG_DFL); /* restore default behavior for SIGINT */ while (1) { FD_ZERO(&readfds); for (i = 0; i < num_rexecs; i++) if (sockarr[i] != BADSOCKET_VALUE) FD_SET(sockarr[i], &readfds); rv = select(num_rexecs, &readfds, NULL, NULL, &timeout); if ( rv == 0) continue; if (rv < 0 && WSAGetLastError() == WSANOTINITIALISED) return; if (rv < 0) errexit("Error in echo_client(): select() returns %d: %s\n", rv, neterrstr()); /* loop over the sockets that are ready with data */ for (i = 0; i < num_rexecs; i++) { if (sockarr[i] != BADSOCKET_VALUE && FD_ISSET(sockarr[i], &readfds)) { len = NETREAD(sockarr[i], buf, sizeof(buf)); if (len <= 0) { /* mark connection closed */ sockarr[i] = BADSOCKET_VALUE; if (len < 0 && WSAGetLastError() == WSANOTINITIALISED) return; if (len < 0) fprintf(stderr, "Error in echo_client() after NETREAD(): %s\n", neterrstr()); continue; } /* copy to stdout or stderr */ fwrite(buf, sizeof(char), len, outfile); } } } D_PRINTF( "Exiting echo_client\n" ); } #else static int echo_client(char *hostname, const int fd) { /* * WRITE TEXT FROM FILE DESCRIPTOR INTO STDOUT */ char buf[BUFSIZ]; int cc; D_PRINTF( "echo_client running\n" ); while (getppid() != 1) { cc = NETREAD(fd, buf, sizeof(buf)); if (cc > 0) { write(STDOUT_FILENO, buf, cc); } } D_PRINTF( "Exiting echo_client\n" ); NETCLOSE(fd); } #endif /* WIN32 */ /* Picks the appropriate webmaster IP address based on the address of the client. * This is significant only for hosts with multiple interfaces * * return value is a string with the IP address or hostname (or NULL) */ char *pick_webmaster_IP_address(char *client_hostname, struct hostent *master_phe, unsigned netmask) { static char buf[20]; unsigned char addr[4]; int client_addr; int i; if (client_hostname[0] >= '0' && client_hostname[0] <= '9') { /* we have an IP address */ client_addr = inet_addr(client_hostname); if (client_addr == INADDR_NONE) return NULL; } else { /* we have a hostname, use the webserver hostname */ return master_phe->h_name; } for (i = 0; master_phe->h_addr_list[i] != NULL; i++) { if ((*(int *)(master_phe->h_addr_list[i]) & netmask) == (client_addr & netmask)) goto gotit; } i = 0; /* give up */ gotit: memcpy((char *)addr, master_phe->h_addr_list[i], sizeof(addr)); /* Internet specific */ sprintf(buf, "%d.%d.%d.%d", addr[0], addr[1], addr[2], addr[3]); return buf; } /* Command line parsing */ void ParseCmdLine(int argc, char **argv ) { char getoptch; int currarg; extern char *optarg; extern int optind; /* * PARSE THE COMMAND LINE OPTIONS */ while((getoptch = getopt(argc,argv,"P:f:t:l:p:u:R:w:n:M:adrsvWX")) != (const char)EOF) { switch(getoptch) { case 'M': strcpy(network_mask_str, optarg); break; case 'P': haveproxyserver = 1; strcpy(proxyserver, optarg); break; case 'R': record_all_transactions = 1; break; case 'X': norexec = 1; break; case 'W': servaddrin_config = 1; break; case 'a': dumpall = 1; break; case 'd': debug = 1; break; case 'f': strcpy(configfile, optarg); break; case 'l': numloops = atoi(optarg); break; case 'p': portnum = atoi(optarg); break; case 'r': redirect = 1; break; case 's': savefile = 1; break; case 't': testtime = atoi(optarg); break; case 'u': uil_filelist_f = 1; strcpy(uil_filelist, optarg); break; case 'v': verbose = 1; break; case 'w': havewebserver = 1; strcpy(webserver, optarg); break; default: usage(argv[0]); } /* end switch */ } /* end while */ if (numloops && testtime) errexit("Can't have both -l and -t\n"); if(!havewebserver && !servaddrin_config) { /* * THE SERVERS NAME MUST BE SPECIFIED */ fprintf(stderr,"No WWW Server specified\n"); usage(argv[0]); } if (havewebserver && servaddrin_config) { /* * CAN'T HAVE BOTH -w and -W */ fprintf(stderr, "Can't have both -w and -W options\n"); usage(argv[0]); } network_mask = inet_addr(network_mask_str); if (network_mask == INADDR_NONE) { fprintf(stderr, "Invalid network mask (-M %s)\n", network_mask_str); usage(argv[0]); } if(strlen(configfile) == 0) { /* * THE MASTER MUST HAVE A CONFIGURATION FILE TO READ. */ fprintf(stderr,"No Configuration file specified\n"); usage(argv[0]); } /* IF WE DO NOT HAVE A FILE LIST THEN THERE ARE UIL'S AT THE END OF THE * COMMAND LINE SO GRAB THEM. */ if (uil_filelist_f == 0) { currarg = optind; number_of_pages = 0; while(currarg != argc) { /* * GET THE UILS TO RETRIEVE. */ sscanf(argv[currarg],"%s",filelist[number_of_pages]); number_of_pages++; currarg++; } } else { /* have filelist; take a stab at the number of valid URLs */ D_PRINTF( "About to parse filelist %s\n", uil_filelist ); number_of_pages = count_file_list(uil_filelist); } if (number_of_pages == 0) { /* * AT LEAST ONE FILE MUST BE SPECIFIED */ fprintf(stderr,"No URL resources specified\n"); usage(argv[0]); } } /* This function sets up the socket we will use to synchronize with the clients. Returns the socket number if successful, doesn't return if it fails */ SOCKET SetupSyncSocket( serveraddr ) struct sockaddr_in *serveraddr; { int sock,len; /* * SET UP THE SOCKET WE ARE GOING TO USE TO SYNCHRONIZE WITH THE CLIENTS. */ D_PRINTF( "About to call sock %d %d\n", portnum, MAXCLIENTS ); sock = passivesock(0, "tcp", MAXCLIENTS); if (BADSOCKET(sock)) { errexit("Couldn't open socket %d: %s\n", sock, neterrstr()); } D_PRINTF( "The passivesock call succeeded\n" ); D_PRINTF( "calling getsockname\n" ); len = sizeof(struct sockaddr); if(getsockname(sock, (struct sockaddr *)serveraddr, &len) < 0) { errexit("Could not get socket informaton\n"); } return( sock ); } /* Function which generates a commandline for the webclients */ void MakeCmdLine( commandline) char *commandline; { char tmpcommandline[NCCARGS]; char hostname[MAXHOSTNAMELEN]; char *webclient_path, *temp; int cnt; struct hostent *master_phe_tmp; /* temp. variable added by - Rajesh Shah*/ /* * BUILD THE PORTIONS OF THE cmdline FOR EACH CLIENT THAT WE CAN BUILD NOW. * WE WILL FILL IN THE NUMBER OF CLIENTS LATER WITH AN sprintf. */ D_PRINTF( "Calling gethostname\n" ); if(gethostname(hostname,MAXHOSTNAMELEN) != 0) { errexit("Could not retrieve local host name"); } else { /* convert hostname to address (to avoid DNS problems for webclients) */ /* The following lines are add to copy the system buffer (output of gethostbyname()) into user area. This is because, there are very good chances that later on system buffer might be overwritten by some calls and still if your pointer is pointing to same addr. nothing but only trouble and trouble! Infact this is what happening when I tried to run webstone benchmark for more then one clients. It used to over write the webmaster name with the first client name and so remaining on client(s) the webclient process(es) were invoked with wrong webmaster name! This behaviour is observed Solaris 2.4 this bug can be hit in any OS. - Rajesh Shah 5/18/96 */ /* master_phe = gethostbyname(hostname); */ master_phe_tmp = gethostbyname(hostname); master_phe = (struct hostent *)malloc(sizeof(struct hostent)); HostEntCpy(master_phe, master_phe_tmp); } /* set up executable pathname */ #ifndef WIN32 temp = getenv("TMPDIR"); if ( temp && *temp ) { webclient_path = (char *)mymalloc( strlen(temp) + strlen("/webclient") + 1); strcpy(webclient_path, temp); strcat(webclient_path, "/webclient"); } else #else temp = temp; #endif /* WIN32 */ webclient_path = PROGPATH; D_PRINTF( "Path to webclient is: %s\n", webclient_path ); sprintf(commandline,"%s", webclient_path); if(haveproxyserver) { sprintf(tmpcommandline, " -P %s", proxyserver); strcat(commandline, tmpcommandline); } if (debug) { strcat(commandline," -d"); } if (numloops != 0) { sprintf(tmpcommandline," -l %d", numloops); strcat(commandline,tmpcommandline); } if (portnum) { sprintf(tmpcommandline," -p %d", portnum); strcat(commandline,tmpcommandline); } if (redirect) { strcat(commandline," -r"); } if (savefile) { strcat(commandline," -s"); } if (uil_filelist_f) { strcat(commandline," -u "); strcat(commandline,uil_filelist); } if (record_all_transactions) { strcat(commandline," -R"); } if (testtime != 0) { sprintf(tmpcommandline," -t %d", testtime); strcat(commandline,tmpcommandline); } /* * SET UP A SPACE FOR THE NUMBER OF CLIENTS ON THE commandline. */ sprintf(tmpcommandline,"%s -n %%d -w %%s -c %%s:%%d", commandline); strcpy(commandline,tmpcommandline); if (uil_filelist_f == 0) { cnt = 0; while(cnt < number_of_pages) { /* * PUT THE FILES AT THE END OF THE LIST. */ strcat(commandline," "); strcat(commandline,filelist[cnt]); cnt++; } } puts(commandline); } /* rexec to the client hosts and start the webclients */ int RexecClients( commandline, clienthostname, serveraddr) char *commandline; char clienthostname[MAXCLIENTS][MAXHOSTNAMELEN]; struct sockaddr_in *serveraddr; { int tmpfd; int numclients = 0; char tmpcommandline[NCCARGS]; struct servent *inetport; int cnt; char buffer[NCCARGS]; char login[MAXUSERNAME]; char password[MAXPASSWD]; FILE *fp; int returnval; char *tmphostname; memset(buffer, 0, sizeof(buffer)); /* * OPEN UP THE CONFIG FILE. FOR EACH LINE IN THE CONFIG FILE, CHECK * ITS VALIDITY AND THEN rexec A COMMAND ON THE CLIENT. */ if ((fp = fopen(configfile,"r")) == NULL) { errexit("Could not open config file %s\n", configfile); } if ((inetport = getservbyname("exec","tcp")) == NULL) { errexit("Could not get service name for exec/tcp\n"); } D_PRINTF( "getservbyname returned %d\n", ntohs(inetport->s_port) ); cnt = 0; D_PRINTF( "rexec loop\n" ); while(1) { char webserver2[MAXHOSTNAMELEN]; char linebuf[150]; int num; char *primename; if (NULL == fgets(linebuf, sizeof(linebuf), fp)) break; num = sscanf(linebuf,"%s %s %s %d %s",clienthostname[cnt],login,password, &numclients, webserver2); if (num < 4) break; if (servaddrin_config) { if (num == 4) { errexit("No webserver specified in config file for %s\n", clienthostname[cnt]); } strcpy(webserver, webserver2); } if (numclients <= 0) errexit("Number of clients must be >= 0\n"); if (numclients > MAXPROCSPERNODE) { errexit("Number of clients per node can't exceed %d\n", MAXPROCSPERNODE); } totalnumclients += numclients; primename = pick_webmaster_IP_address(clienthostname[cnt], master_phe, network_mask); if (primename == NULL) { errexit("Bad client address %s for Client %d\n", clienthostname[cnt], cnt); } fprintf(stdout,"Client %d: %s \t# Processes: %d\n Webserver: %s\tWebmaster: %s:%d\n", cnt, clienthostname[cnt], numclients, webserver, primename, ntohs(serveraddr->sin_port)); fflush(stdout); sprintf(tmpcommandline, commandline, numclients, webserver, primename, ntohs(serveraddr->sin_port)); fprintf(stderr, "tmpcommandline: %s\n", tmpcommandline); D_PRINTF( "%s rexec %s\n",&clienthostname[cnt],tmpcommandline ); if (norexec) { sleep(30); /* gives some time to start clients for debugging */ } else { tmphostname = &(clienthostname[cnt][0]); tmpfd = rexec(&tmphostname, inetport->s_port, login, password, tmpcommandline, &sockErr[cnt]); if((sockIO[cnt] = tmpfd) < 0) { errexit("Could not rexec: rexec to client %s, cmdline %s failed\n", clienthostname[cnt],tmpcommandline); } } returnval = NETREAD(tmpfd, buffer, OKSTRLEN); D_PRINTF( "read returns %d, %s\n", returnval, buffer ); if (returnval <= 0 || memcmp(buffer, OKSTR, OKSTRLEN) != 0) { errexit("rexec to client %s, cmdline %s received error %s\n", clienthostname[cnt],tmpcommandline, buffer); } cnt++; if (cnt > MAXCLIENTS || cnt > FD_SETSIZE) { errexit("Number of Clients can't exceed %d\n", MAXCLIENTS); } } num_rexecs = cnt; if (totalnumclients > MAXTOTALPROCS) { errexit("Total number of processes can't exceed %d\n", MAXTOTALPROCS); } #ifndef WIN32 /* NOW WE NEED TO HANDLE THE OUTPUT FROM THE REXEC. * TO DO THIS, WE FORK, THEN HAVE ONE PROCESS READ FROM TMPFD. * THE OTHER PROCESS CONTINUES WITH THE PROGRAM */ D_PRINTF( "Forking webclient stderr/stdout processes\n" ); switch (fork()) { case -1: /* ERROR */ errexit("fork: %s\n", strerror(errno)); case 0: /* CHILD */ exit(echo_client(clienthostname[cnt], tmpfd)); default: /* PARENT */ break; } #else /* start threads to echo stdout/stderr from clients */ _beginthread(echo_client, 0, (void *)0); _beginthread(echo_client, 0, (void *)1); #endif /* WIN32 */ fprintf(stdout,"\n"); fprintf(stdout,"\n"); fclose(fp); return totalnumclients; } void GetReady( fdset, totalnumclients, sock ) fd_set *fdset; int totalnumclients; int sock; { int cnt,len; fd_set tmpfdset, leftfdset; char buffer[NCCARGS]; /* * NOW WE NEED TO ACCEPT ALL THE CONNECTIONS FROM THE CLIENTS, * ACCEPT ALL THE READY MESSAGES */ D_PRINTF( "Beginning accept loop\n" ); for (cnt = 0; cnt < totalnumclients; cnt++) { D_PRINTF( "Client %d:\t", cnt ); { fd_set readfds; struct timeval timeout; int rv; timeout.tv_sec = MAX_ACCEPT_SECS; timeout.tv_usec = 0; FD_ZERO(&readfds); FD_SET(sock, &readfds); /* if we're hung, quit */ D_PRINTF("Before select() on listen() socket\n"); if (!(rv = select(FD_SETSIZE, &readfds, NULL, NULL, &timeout))) { fprintf(stdout, "Listen timeout after %d seconds (%d clients so far)\n", MAX_ACCEPT_SECS, cnt); D_PRINTF("select() timed out after %d seconds\n", MAX_ACCEPT_SECS); errexit("Webmaster terminating\n"); } } if(BADSOCKET(socknum[cnt] = accept(sock, NULL, 0))) { /* * ERROR accepting FROM THE CLIENTS. WE NEED TO ISSUE AN * ABORT TO ALL. */ abort_clients(); errexit("Error accepting from one of the clients: %s", neterrstr()); } else { /* * SET THE FD IN THE MASK */ FD_SET(socknum[cnt],fdset); } D_PRINTF( "on socket %d\n",socknum[cnt] ); } D_PRINTF( "\n" ); /* * WAIT FOR A READY. */ sleep(1); fprintf(stdout,"Waiting for READY from %d clients\n",totalnumclients); fflush(stdout); leftfdset = *fdset; #ifndef WIN32 while(memcmp(&leftfdset,&zerofdset,sizeof(fd_set))) { tmpfdset = leftfdset; if(select(FD_SETSIZE,&tmpfdset,NULL,NULL,NULL) < 0) { /* * ERROR SELECTING. ABORT ALL. */ abort_clients(); errexit("Error accepting from one of the clients: %s\n", neterrstr()); break; } #else /* I don't see why a select is needed at all--all clients must respond * and there is no synchronization/timing issue. */ tmpfdset = leftfdset; { #endif /* WIN32 */ for (cnt = 0; cnt < totalnumclients; cnt++) { /* * SEE WHICH SOCKETS HAVE A INPUT ON THEM PENDING * AND RECEIVE IT. */ if(!BADSOCKET(socknum[cnt]) && (FD_ISSET(socknum[cnt],&tmpfdset))) { /* * GET THE READY FROM THIS GUY. * DON'T FORGET TO CLEAR HIS BIT IN THE tmpfdset */ len = NETREAD(socknum[cnt],buffer,READYSTRLEN); if(len != READYSTRLEN) { abort_clients(); errexit("Error reading from client #%d\n", cnt); } if(memcmp(buffer, READYSTR, READYSTRLEN)) { abort_clients(); fprintf(stdout,"Received bad READY string: len %d, value %s\n", len,buffer); } FD_CLR(socknum[cnt],&leftfdset); } } } sleep(1); fprintf(stdout,"All READYs received\n"); fflush(stdout); } /* Start all the clients by sending them a GO signal totalnumclients is the total number of clients socknum is an int array with the filedescriptors for all the client connections */ void SendGo( totalnumclients, socknum) int totalnumclients; int *socknum; { int cnt; fprintf(stdout,"Sending GO to all clients\n"); for(cnt = 0; cnt < totalnumclients; cnt++) { if(socknum[cnt] > 0) { /* * SEND A GO */ if(NETWRITE(socknum[cnt], GOSTR, GOSTRLEN) != GOSTRLEN) { abort_clients(); errexit("Error sending GO to client %d: %s\n", cnt, neterrstr()); } } } } /* This function gathers statistics from all the clients */ void GetResults(fdset, page_stats, endtime, timestr, totalnumclients, statarray) fd_set *fdset; page_stats_t **page_stats; time_t *endtime; char *timestr; int totalnumclients; stats_t statarray[MAXCLIENTS]; { fd_set leftfdset,tmpfdset; char *stats_as_text; char *page_stats_as_text; int returnval; int cnt,i; /* DOESN'T ACTUALLY PRINT UNTIL THE FIRST CLIENT REPORTS */ fprintf(stdout,"Reading results "); /* * COPY THE FILE DESCRIPTORS TO A TMP LIST, * ALLOCATE MEMORY FOR STATS, PAGESTATS IN TEXT FORM */ leftfdset = *fdset; stats_as_text = (char *)mymalloc(SIZEOF_STATSTEXT+1); page_stats_as_text = (char *)mymalloc(SIZEOF_PAGESTATSTEXT+1); /* * COPY THE FILE DESCRIPTORS TO A TMP LIST, * PLUS A LIST OF REMAINING FDs */ leftfdset = *fdset; /* * LOOP UNTIL ALL CLIENTS HAVE REPORTED * AND tmpfdset IS EMPTY */ #ifndef WIN32 while(memcmp(&leftfdset,&zerofdset,sizeof(fd_set))) { tmpfdset = leftfdset; sleep(1); returnval = select(FD_SETSIZE,&tmpfdset,NULL,NULL,NULL); D_PRINTF( "Call to select returned %d, errno %d\n", returnval, errno ); if(returnval < 0) { /* * ERROR SELECTING. ABORT ALL. */ D_PRINTF( "select() error %s\n", neterrstr() ); abort_clients(); errexit("Error selecting from one of the clients\n"); } #else /* I don't see why a select is needed at all */ tmpfdset = leftfdset; { #endif /* WIN32 */ for(cnt = 0; cnt < totalnumclients; cnt++) { /* * SEE WHICH SOCKETS HAVE A INPUT ON THEM PENDING AND * RECEIVE IT. */ /* IS THIS A VALID SOCKET? IS IT READY TO READ? */ if(!BADSOCKET(socknum[cnt]) && (FD_ISSET(socknum[cnt],&tmpfdset))) { int len; /* * GET THE TIMING DATA FROM THIS GUY * THEN REMOVE HIM FROM THE tmpfdset */ /* * READ TIME STATS * DOES READ() RETURN THE CORRECT LENGTH? */ D_PRINTF( "About to read timestats, count %d, errno %d\n", cnt, errno ); len = SIZEOF_STATSTEXTBASE + number_of_pages*SIZEOF_DOUBLETEXT; returnval = recvdata(socknum[cnt], stats_as_text, len); D_PRINTF( "Read time stats %d\n", returnval ); if (returnval != len) /* <= 0) */ { D_PRINTF( "Error reading timing stats: %s\n", neterrstr() ); fprintf(stderr, "Error reading timing stats: %s\nSocket number %d\n", neterrstr(),socknum[cnt]); abort_clients(); errexit(""); } /* end if */ /* convert text to stats */ stats_as_text[returnval] = 0; /* add an end marker */ statarray[cnt] = *text_to_stats(stats_as_text); fputc('.', stdout); /* PROGRESS MARKER */ fflush(stdout); if(uil_filelist_f) /* READ PAGE STATS */ { for (i = 0; i < number_of_pages; i++) { D_PRINTF( "On page_stats[%d][%d]\n", cnt, i ); returnval = recvdata(socknum[cnt], page_stats_as_text, SIZEOF_PAGESTATSTEXT); D_PRINTF( "Read page stats %d\n", returnval ); if (returnval != SIZEOF_PAGESTATSTEXT) /* <= 0) */ { D_PRINTF( "Error reading page_stats[%d][%d]: %s\n", cnt, i, neterrstr() ); fprintf(stderr, "Error reading page_stats[%d][%d]: %s\n", cnt, i, neterrstr()); abort_clients(); errexit(""); } D_PRINTF( "Page stats: read %d bytes\n", returnval ); page_stats_as_text[returnval] = 0; /* add an end marker */ D_PRINTF("strlen(page_stats_as_text) = %d\n", strlen(page_stats_as_text)); page_stats[cnt][i] = *text_to_page_stats(page_stats_as_text); } /* end for */ } /* end if filelist */ FD_CLR(socknum[cnt],&leftfdset); NETCLOSE(socknum[cnt]); socknum[cnt] = BADSOCKET_VALUE; } /* end if socknum */ } /* end for cnt */ } /* end while memcmp fd */ /* * DONE READING RESULTS FROM CLIENTS */ *endtime = time(NULL); timestr = asctime(localtime(endtime)); fprintf(stdout,"\nAll clients ended at %s\n",timestr); fflush(stdout); /* FREE MEMORY ALLOCATED FOR CLIENT STATS, PAGESTATS AS TEXT */ free(stats_as_text); free(page_stats_as_text); } /* Prints out all the results */ void PrintResults( page_stats, endtime, timestr, totalnumclients, statarray, page_stats_total) page_stats_t **page_stats; time_t endtime; char *timestr; int totalnumclients; stats_t statarray[MAXCLIENTS]; page_stats_t *page_stats_total; { stats_t masterstat; int cnt,i,j; double thruput; struct timeval dtime; /* * PRINT EVERYTHING OUT */ stats_init(&masterstat); for(cnt = 0; cnt < totalnumclients; cnt++) { if((statarray[cnt].rs.totalconnects > 0) && (dumpall)) { fprintf(stdout,"----------------------------------\n"); /* fprintf(stdout,"Test for host: %s\n",statarray[cnt].hostname); */ fprintf(stdout,"Total number of pages retrieved from server: %u\n", statarray[cnt].totalpages); rqstat_fprint(stdout, &(statarray[cnt].rs)); thruput = thruputpersec((double)(statarray[cnt].rs.totalbytes), &(statarray[cnt].rs.totalresponsetime)); fprintf(stdout, "Thruput average per connection: %.0f bytes/sec\n", thruput); } if(statarray[cnt].rs.totalconnects > 0) { D_PRINTF( "Summing stats for %d, with %ld total connections\n", cnt, statarray[cnt].rs.totalconnects ); rqstat_sum(&masterstat.rs, &(statarray[cnt].rs)); } else { masterstat.rs.totalerrs += statarray[cnt].rs.totalerrs; } } for (i=0; i < totalnumclients; i++) { for (j=0; j < number_of_pages; j++) { D_PRINTF( "Summing page stats for %d, page %d, with %d connects\n", i, j, statarray[i].page_numbers[j] ); if (statarray[i].page_numbers[j] != 0) { rqst_stats_t *pst_rs; rqst_stats_t *ps_rs; pst_rs = &(page_stats_total[j].rs); ps_rs = &(page_stats[i][j].rs); rqstat_sum(pst_rs, ps_rs); page_stats_total[j].totalpages += page_stats[i][j].totalpages; masterstat.totalpages += page_stats[i][j].totalpages; /* yes, this is assignment, not sum */ page_stats_total[j].page_size = page_stats[i][j].page_size; page_stats_total[j].page_valid = 1; } } } /* print page statistics */ if (verbose) { for (i = 0; i < number_of_pages; i++) { if (page_stats_total[i].page_valid == 1) { page_stats_t *pst; pst = &(page_stats_total[i]); printf ("===============================================================================\n"); printf ("Page # %d\n\n", i); printf ("Total number of times page was hit %u\n", pst->totalpages); rqstat_print(&(pst->rs)); printf ("Page size %u \n", pst->page_size); printf ("===============================================================================\n\n"); } } } fprintf(stdout,"===============================================================================\n"); /* * Validate run. */ masterstat.total_num_of_files = statarray[0].total_num_of_files; for (i=1; i < totalnumclients; i++) { if ((statarray[i].rs.totalconnects > 0) && (statarray[i].total_num_of_files != masterstat.total_num_of_files)) { fprintf(stdout,"**********************************************************************\n"); fprintf(stdout,"**** ERROR: number of files in each test configuration is not the same\n"); fprintf(stdout,"**** ERROR: Check configuration file %s on each client\n", configfile); fprintf(stdout,"**********************************************************************\n"); break; } } /* * Print summary statistics */ fprintf(stdout, "WEBSTONE 2.0 results:\n"); fprintf(stdout, "Total number of clients: \t%d\n", totalnumclients); testtime = sumedh_end.tv_sec - sumedh_start.tv_sec; fprintf(stdout,"Test time: \t\t\t%d seconds\n", testtime); fprintf(stdout, "Server connection rate: \t%3.2f connections/sec\n", (double)(masterstat.rs.totalconnects)/(testtime)); fprintf(stdout, "Server error rate: \t\t%4.4f err/sec\n", (double)(masterstat.rs.totalerrs)/(testtime)); fprintf(stdout, "Server thruput: \t\t%2.2f Mbit/sec\n", (double)(8*masterstat.rs.totalbytes)/(testtime*1024*1024)); fprintf(stdout, "Little's Load Factor: \t\t%3.2f \n", (double)(masterstat.rs.totalresponsetime.tv_sec) /(testtime)); avgtime(&masterstat.rs.totalresponsetime, masterstat.rs.totalconnects, &dtime); fprintf(stdout, "Average response time: \t\t%4.4f millisec\n", (double)1000*(dtime.tv_sec + (double)dtime.tv_usec / 1000000)); fprintf(stdout, "Error Level:\t\t\t%4.4f %%\n", (double)(100 * masterstat.rs.totalerrs)/(masterstat.rs.totalconnects)); /* so much for the key metrics */ thruput = 8 * thruputpersec((double)(masterstat.rs.totalbytes), &(masterstat.rs.totalresponsetime)); fprintf(stdout, "Average client thruput: \t%4.4f Mbit/sec\n", thruput/(1024*1024)); fprintf(stdout,"Sum of client response times:\t%u.%u sec\n", masterstat.rs.totalresponsetime.tv_sec, masterstat.rs.totalresponsetime.tv_usec); fprintf(stdout,"Total number of pages read:\t%u\n\n", masterstat.totalpages); /* Remaining stats are the same as usual */ rqstat_fprint(stdout, &(masterstat.rs)); fflush(stdout); } #ifdef WIN32 /* close socket library */ void sock_cleanup(void) { WSACleanup(); } #endif /* WIN32 */ void main(const int argc, char *argv[]) { int sync_sock; int i; int j; char buffer[NCCARGS]; char commandline[NCCARGS]; char *timestr; time_t starttime; time_t endtime; fd_set fdset; /* make the big arrays static to avoid stack overflow */ static char clienthostname[MAXCLIENTS][MAXHOSTNAMELEN]; static stats_t statarray[MAXCLIENTS]; page_stats_t **page_stats; page_stats_t *page_stats_total; struct sockaddr_in serveraddr; #ifdef WIN32 WSADATA WSAData; COORD dwSize; if ((WSAStartup(MAKEWORD(1,1), &WSAData)) != 0) { errexit("Error in WSAStartup()\n"); } atexit(sock_cleanup); /* increase size of output window */ dwSize.X = 80; dwSize.Y = 500; SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), dwSize); #endif /* WIN32 */ /* Initalization of variables. */ debugfile = stdout; memset(buffer, 0, NCCARGS); memset(webserver, 0, MAXHOSTNAMELEN); memset(configfile, 0, MAXPATHLEN); FD_ZERO(&zerofdset); FD_ZERO(&fdset); for(i = 0; i < MAXCLIENTS; i++) { socknum[i] = BADSOCKET_VALUE; statarray[i].rs.totalconnects = 0; } signal(SIGINT, sig_int); ParseCmdLine( argc, argv); sync_sock = SetupSyncSocket( &serveraddr ); MakeCmdLine( commandline); totalnumclients = RexecClients( commandline, clienthostname, &serveraddr); /* Initalization of variables. */ page_stats = (page_stats_t **) mymalloc(totalnumclients*sizeof(page_stats_t *)); for (i=0; i < totalnumclients; i++) { page_stats[i] = (page_stats_t *) mymalloc(number_of_pages*sizeof(page_stats_t)); } page_stats_total = (page_stats_t *)mymalloc(number_of_pages*sizeof(page_stats_t)); for (i=0; i < totalnumclients; i++) { stats_init(&(statarray[i])); } for (i=0; i < totalnumclients; i++) { for (j=0; j < number_of_pages; j++) { page_stats_init(&(page_stats[i][j])); } } for (i=0; i < number_of_pages; i++) { page_stats_init(&(page_stats_total[i])); } for(i = 0; i < totalnumclients; i++) { socknum[i] = BADSOCKET_VALUE; statarray[i].rs.totalconnects = 0; } GetReady( &fdset, totalnumclients, sync_sock ); NETCLOSE(sync_sock); /* * START ALL OF THE CLIENTS BY SENDING THEM A GO SIGNAL. */ gettimeofday (&sumedh_start, NULL); SendGo( totalnumclients, socknum); /* * WAIT FOR ALL OF THE CLIENTS TO COMPLETE. WE SHOULD GET A REPLY * FOR EACH SOCKET WE HAVE OPEN. THE REPLY WILL BE THE TIMING * INFORMATION WE USE. */ starttime = time(NULL); timestr = asctime(localtime(&starttime)); fprintf(stdout,"All clients started at %s\n",timestr); fprintf(stdout,"Waiting for clients completion\n"); fflush(stdout); /* IF THIS IS A TIMED TEST, WE MIGHT AS WELL SNOOZE */ if (testtime) { sleep(testtime * 60); } GetResults( &fdset, page_stats, &endtime, timestr, totalnumclients, statarray); gettimeofday (&sumedh_end, NULL); PrintResults( page_stats, endtime, timestr, totalnumclients, statarray, page_stats_total); /* free memory */ for (i = 0; i < totalnumclients; i++) { free(page_stats[i]); } free(page_stats); free(page_stats_total); exit(0); } /* Added by Rajesh Shah 5/18/96 */ void HostEntCpy(struct hostent *dest, struct hostent *src) { dest->h_name = (char *)malloc(strlen(src->h_name)+1); strcpy(dest->h_name, src->h_name); printf("WebMaster name = %s\n", dest->h_name); dest->h_aliases = src->h_aliases; dest->h_addrtype = src->h_addrtype; dest->h_length = src->h_length; dest->h_addr_list = src->h_addr_list; }