/**************************************************************************** * * * The contents of this file are subject to the WebStone Public License * * Version 1.0 (the "License"); you may not use this file except in * * compliance with the License. You may obtain a copy of the License * * at http://www.mindcraft.com/webstone/license10.html * * * * Software distributed under the License is distributed on an "AS IS" * * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * * the License for the specific language governing rights and limitations * * under the License. * * * * The Original Code is WebStone 2.5. * * * * The Initial Developer of the Original Code is Silicon Graphics, Inc. * * and Mindcraft, Inc.. Portions created by Silicon Graphics. and * * Mindcraft. are Copyright (C) 1995-1998 Silicon Graphics, Inc. and * * Mindcraft, Inc. All Rights Reserved. * * * * Contributor(s): ______________________________________. * * * * @(#) bench.c 2.4@(#) * ***************************************************************************/ #include #include #include #include #include #include #include #ifndef WIN32 #include #include #include #include #include #include #include #include #else /* WIN32 */ #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 = DEBUG_OFF; 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 record_all_transactions = 0; int use_fixed_random_seed = 0; int verbose = 0; char webserver[MAXHOSTNAMELEN]; char configfile[MAXPATHLEN]; char webclient_path[MAXPATHLEN]; char debug_filename[MAXPATHLEN]; int uil_filelist_f = 0; char uil_filelist[MAXPATHLEN]; char child_uil_filelist[MAXPATHLEN]; char filelist[256][MAXPATHLEN]; int randomize = 0; fd_set zerofdset; /* other key data */ long int number_of_pages = 0; long int num_input_lines = 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 */ static void usage(const char *progname) { fprintf(stderr, "Usage: %s [-a] [-d] -f config_file -C webclient_path\n", progname); fprintf(stderr, " [-t run_time | -l num_loops] [-p port_num]\n"); fprintf(stderr, " [-r] [-s] [-v] [-w webserver_URL] \n"); fprintf(stderr, " [-u masterfilelist] [-U clientfilelist] [-W]\n"); fprintf(stderr, " [-P proxy_server] [-R] [-S]\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, "-s save client gets to /tmp/webstone.data.*\n"); fprintf(stderr, "-t run_time\tduration of test in minutes\n"); fprintf(stderr, "-u URL file\tfilelist of URLs for webmaster\n"); fprintf(stderr, "-U URL file\tfilelist of URLs for webclient\n"); fprintf(stderr, "-v verbose mode\n"); fprintf(stderr, "-D debugfile\tFile to receive debug data on client\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"); fprintf(stderr, "-S used fixed seed for random number generator\n"); fprintf(stderr, "-T turn on trace statements\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 */ D_PRINTF("Entering passivesock(): portnum=%d, protocol='%s', qlen=%d\n", portnum, protocol, qlen); memset((char *)&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = INADDR_ANY; errno = 0; sin.sin_port = htons(portnum); /* Map protocol name to number */ CLEAR_SOCK_ERR; if ((ppe = getprotobyname(protocol)) == NULL) errexit("Can't get \"%s\" protocol entry\n", protocol); /* allocate a socket */ CLEAR_SOCK_ERR; s = socket(PF_INET, SOCK_STREAM, ppe->p_proto); if (BADSOCKET(s)) { D_PRINTF("socket() returned %d. p_proto=%d, errno=%d (%s)", s, ppe->p_proto, s, GET_NET_ERR, neterrstr()); errexit("Can't create socket: %s\n", neterrstr()); } /* Bind the socket */ CLEAR_SOCK_ERR; if (SOCK_ERR(bind(s, (struct sockaddr *)&sin, sizeof(sin)))) errexit("Can't bind to port %d: %s\n", portnum, neterrstr()); /* * If it's a stream, listen for connections. * On NT and many UNIX systems, the backlog parameter is silently * limited to 5 connections. */ CLEAR_SOCK_ERR; if (SOCK_ERR(listen(s, qlen))) errexit("Can't listen on port %s: %s\n", portnum, neterrstr()); return s; } /* * abort_clients() * Called by SIGINT handler as well as other error handlers. * This just does an exit() which will close all socket connections. * This doesn't explicitly kill the clients but they will get an error * and die when they try to send results to the webmaster. If any * children are stuck then they'll have to be killed by hand. */ static void abort_clients(void) { exit(2); } /* signal handler for SIGINT */ static void sig_int(int sig) { /* its not safe to call fprintf() from a signal handler. */ abort_clients(); } #ifdef WIN32 /* echo stdout/stderr from clients */ void echo_client(void *arg) { SOCKET *sockarr; char buf[BUFSIZ]; int i, len, rv; fd_set readfds; struct timeval timeout; int is_err = (int) arg; int fdout; int anyleft; timeout.tv_sec = 5L; timeout.tv_usec = 0L; if (is_err) { sockarr = sockErr; fdout = _fileno(stderr); } else { sockarr = sockIO; fdout = _fileno(stdout); } while (1) { FD_ZERO(&readfds); for (i = anyleft = 0; i < num_rexecs; i++) { if (sockarr[i] != BADSOCKET_VALUE) { FD_SET(sockarr[i], &readfds); anyleft++; } } if (!anyleft) return; WSASetLastError(0); rv = select(num_rexecs, &readfds, NULL, NULL, &timeout); if (rv == 0) continue; if (rv < 0) { if (WSAGetLastError() == WSANOTINITIALISED) return; errexit("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) { if (WSAGetLastError() == WSANOTINITIALISED) return; fprintf(stderr, "Error in echo_client() after NETREAD(): %s\n", neterrstr()); continue; } write(fdout, buf, len); /* copy to stdout or stderr */ } } } } #else static int echo_client(const int fd, int is_err) { /* * WRITE TEXT FROM FILE DESCRIPTOR INTO STDOUT */ char buf[BUFSIZ]; int cc; D_PRINTF("entering echo_client() for %s\n", (is_err ? "stderr" : "stdout")); while (getppid() != 1) { cc = NETREAD(fd, buf, sizeof(buf)); /* * If this is the error stream (stderr of the clients) then only * print it to our stdout if we are in debugging mode. */ if ((cc > 0) && is_err && DEBUGGING_ON) write(STDOUT_FILENO, buf, cc); } D_PRINTF("Exiting echo_client() for %s\n", (is_err ? "stderr" : "stdout")); 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; /* Uncomment the following line to let clients do their own * hostname lookup to find the webmaster system. */ /* return master_phe->h_name; */ if (isdigit(client_hostname[0])) { /* 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 webmaster 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)) { memcpy((char *)addr, master_phe->h_addr_list[i], sizeof(addr)); sprintf(buf, "%d.%d.%d.%d", addr[0], addr[1], addr[2], addr[3]); return buf; } } /* No common net - can't give a verified hostname to webclient */ return NULL; } /* * Command line parsing */ static void ParseCmdLine(int argc, char **argv ) { int getoptch; int currarg; extern char *optarg; extern int optind; FILE *filelist_fp; /* * PARSE THE COMMAND LINE OPTIONS */ while((getoptch = getopt(argc,argv,"aC:dD:f:l:M:n:p:P:R:sSt:Tu:U:vw:WX")) != EOF) { switch(getoptch) { case 'C': strcpy(webclient_path, optarg); break; case 'D': strcpy(debug_filename, optarg); break; case 'M': strcpy(network_mask_str, optarg); break; case 'P': haveproxyserver = 1; strcpy(proxyserver, optarg); break; case 'R': record_all_transactions = 1; break; case 'S': use_fixed_random_seed = 1; break; case 'T': debug |= DEBUG_TRACE; break; case 'U': strcpy(child_uil_filelist, optarg); break; case 'X': norexec = 1; break; case 'W': servaddrin_config = 1; break; case 'a': dumpall = 1; break; case 'd': debug = DEBUG_ALL; break; case 'f': strcpy(configfile, optarg); break; case 'l': numloops = atoi(optarg); break; case 'p': portnum = atoi(optarg); 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: fprintf(stderr,"-%c: Flag not recognized\n", (char)getoptch); 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(webclient_path) == 0) { fprintf(stderr,"No path specified for the webclient program\n"); 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]); } /* * Get the list of UILs to retrieve from the server. The * page count here may include comment lines. We'll get the * actual count from the webclient program later. */ if (uil_filelist_f == 1) { D_PRINTF( "webmaster(): About to read filelist %s\n", uil_filelist ); filelist_fp = fopen(uil_filelist, "r"); if (filelist_fp == NULL) { D_PRINTF( "Error %d opening filelist %s: %s\n", errno, uil_filelist, strerror(errno) );; errexit("Error %d opening filelist %s: %s\n", errno, uil_filelist, strerror(errno)); } while (fgets(filelist[num_input_lines], MAXPATHLEN, filelist_fp) != (char *)NULL) num_input_lines++; fclose(filelist_fp); } else if (optind < argc) /* UILs are on the command line */ { currarg = optind; while(currarg != argc) { /* * GET THE UILS TO RETRIEVE. */ sscanf(argv[currarg],"%s",filelist[num_input_lines]); num_input_lines++; currarg++; } } else if (*child_uil_filelist) { num_input_lines=1; /* Bogus value; trust that webclient has a file */ } else /* Get UILs from stdin */ { while (fgets(filelist[num_input_lines], MAXPATHLEN, stdin) != (char *)NULL) num_input_lines++; } if (num_input_lines == 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(struct sockaddr_in *serveraddr) { int sock, len; D_PRINTF("entering SetupSyncSocket()\n"); /* * passivesock() will exit if there is any error so the returned * socket is always valid. */ sock = passivesock(0, "tcp", MAXCLIENTS); len = sizeof(struct sockaddr); CLEAR_SOCK_ERR; if (SOCK_ERR(getsockname(sock, (struct sockaddr *)serveraddr, &len))) errexit("Could not get socket informaton\n"); return sock; } /* Added by Rajesh Shah 5/18/96 */ int HostEntCpy(struct hostent *dest, struct hostent *src) { size_t count; char **sptr, **dptr; struct in_addr *iptr; 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; /* * ADDED: by Greg Burrell of Mindcraft Inc. 10/22/96 * PROBLEM: we can't just do the assignment: * * dest->h_addr_list = src->h_addr_list * * because those are just pointers and the memory pointed to * may get overwritten during the next gethostbyname() call. * * FIX: Make a copy of the h_addr_list of a hostent structure. */ for(count = 0, sptr = src->h_addr_list; *sptr != NULL; sptr++, count++); if ((dest->h_addr_list = malloc(count + 1)) == NULL) return 0; if ((iptr = malloc(count * sizeof(struct in_addr))) == NULL) return 0; for (sptr = src->h_addr_list, dptr = dest->h_addr_list; *sptr != NULL; sptr++, dptr++, iptr++) { *iptr = *((struct in_addr *) *sptr); *dptr = (char *) iptr; } *dptr = NULL; return 1; } /* * Function which generates a commandline for the webclients */ void MakeCmdLine(char *commandline) { char tmpcommandline[NCCARGS]; char hostname[MAXHOSTNAMELEN]; struct hostent *master_phe_tmp; /* * 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" ); CLEAR_SOCK_ERR; if(SOCK_ERR(gethostname(hostname,MAXHOSTNAMELEN))) errexit("Could not retrieve local host name"); /* Convert hostname to address, to avoid DNS problems for webclients. */ /* Copy the output of gethostbyname() to our own storage, * so it isn't overwritten by a later call to gethostbyname(). */ master_phe_tmp = gethostbyname(hostname); if (master_phe_tmp == NULL) errexit("Unable to resolve webmaster hostname '%s'\n", hostname); master_phe = (struct hostent *)malloc(sizeof(struct hostent)); HostEntCpy(master_phe, master_phe_tmp); sprintf(commandline,"%s", webclient_path); if(haveproxyserver) { sprintf(tmpcommandline, " -P %s", proxyserver); strcat(commandline, tmpcommandline); } if (DEBUGGING_ON) strcat(commandline, " -d"); else if (TRACING_ON) strcat(commandline, " -T"); if (*debug_filename) { strcat(commandline," -D "); strcat(commandline,debug_filename); } if (numloops != 0) { sprintf(tmpcommandline," -l %d", numloops); strcat(commandline,tmpcommandline); } if (portnum) { sprintf(tmpcommandline," -p %d", portnum); strcat(commandline,tmpcommandline); } if (savefile) strcat(commandline," -s"); if (record_all_transactions) strcat(commandline, " -R"); if (testtime != 0) { sprintf(tmpcommandline," -t %d", testtime); strcat(commandline,tmpcommandline); } if (*child_uil_filelist) { sprintf(tmpcommandline," -u %s", child_uil_filelist); strcat(commandline,tmpcommandline); } /* * SET UP A SPACE FOR THE NUMBER OF CLIENTS ON THE commandline. */ sprintf(tmpcommandline, (use_fixed_random_seed ? "%s -n %%d -w %%s -c %%s:%%d -S %%d" : "%s -n %%d -w %%s -c %%s:%%d"), commandline); strcpy(commandline,tmpcommandline); } /* * rexec to the client hosts and start the webclients */ int RexecClients(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; int client_random_seed_base = 0; int i; D_PRINTF( "Entering RexecClients(\"%s\", \"%s\", %lu)\n", commandline, clienthostname[0], (long)serveraddr); fflush(debugfile); /* * OPEN UP THE CONFIG FILE. FOR EACH LINE IN THE CONFIG FILE, CHECK * ITS VALIDITY AND THEN rexec A COMMAND ON THE CLIENT. */ errno = 0; D_PRINTF("Opening configfile: %s\n", configfile); if ((fp = fopen(configfile,"r")) == NULL) errexit("Could not open config file %s\n", configfile); CLEAR_SOCK_ERR; 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; 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); D_PRINTF(" %s %s %s %d %s\n", 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) continue; /* no work for this client system */ 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); if (use_fixed_random_seed) { sprintf(tmpcommandline, commandline, numclients, webserver, primename, ntohs(serveraddr->sin_port), client_random_seed_base); client_random_seed_base += numclients; } else sprintf(tmpcommandline, commandline, numclients, webserver, primename, ntohs(serveraddr->sin_port)); 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]); D_PRINTF("rexec( %s, %d, %s, %s, \"%s,\" %d)\n", tmphostname, inetport->s_port, login, password, tmpcommandline, sockErr[cnt]); tmpfd = rexec(&tmphostname, inetport->s_port, login, password, tmpcommandline, &sockErr[cnt]); if(BADSOCKET(sockIO[cnt] = tmpfd)) { errexit("Could not rexec: rexec to client %s, cmdline %s failed\n", clienthostname[cnt],tmpcommandline); } /* * Send the list of UILs to the webclient process, before it * forks */ if (!(*child_uil_filelist)) { for (i = 0; i < num_input_lines; i++) { D_PRINTF("webmaster: sending <%s> to client, socket %d.\n", filelist[i], sockIO[cnt]); NETWRITE(tmpfd, filelist[i], strlen(filelist[i])); } } D_PRINTF("webmaster: sending <%s> to client, socket %d.\n", ENDTOKEN, tmpfd); /* * write an extra newline in case the last line of the filelist * didn't end in a newline. */ NETWRITE(tmpfd, "\n", 1); NETWRITE(tmpfd, ENDTOKEN, ENDTOKENSTRLEN); memset(buffer, 0, sizeof(buffer)); if (NETREAD(tmpfd, buffer, 10) <= 0) { D_PRINTF( "Error reading number of pages: %s\n", neterrstr()); fprintf(stderr, "Error reading number of pages: %s\nSocket number %d\n", neterrstr(),tmpfd); abort_clients(); errexit(""); } sscanf(buffer, "%d", &number_of_pages); memset(buffer, 0, sizeof(buffer)); if (NETREAD(tmpfd, buffer, 10) <= 0) { D_PRINTF( "Error reading randomize flag: %s\n", neterrstr()); fprintf(stderr, "Error reading randomize flag: %s\nSocket number %d\n", neterrstr(),tmpfd); abort_clients(); errexit(""); } sscanf(buffer, "%d", &randomize); D_PRINTF( "randomize set to %d.\n", randomize); memset(buffer, 0, sizeof(buffer)); returnval = NETREAD(tmpfd, buffer, OKSTRLEN); if (returnval <= 0 || memcmp(buffer, OKSTR, OKSTRLEN) != 0) { errexit("rexec to client %s, cmdline %s received error %s\n", clienthostname[cnt],tmpcommandline, buffer); } D_PRINTF("Received OK from %s.\n", clienthostname[cnt] ); } #ifndef WIN32 /* * Set up processes to read the stdout and stderr of the * rexec(). */ D_PRINTF( "Forking webclient stdout process\n" ); switch (fork()) { case -1: /* ERROR */ errexit("fork: %s\n", strerror(errno)); case 0: /* CHILD */ exit(echo_client(sockIO[cnt], 0)); default: /* PARENT */ break; } D_PRINTF( "Forking webclient stderr process\n" ); switch (fork()) { case -1: /* ERROR */ errexit("fork: %s\n", strerror(errno)); case 0: /* CHILD */ exit(echo_client(sockErr[cnt], 1)); default: /* PARENT */ break; } #endif /* ! WIN32 */ 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); #ifdef WIN32 /* 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; } /* * GetReady() * When each client thread or process starts up it makes a connection * back to the web master. */ void GetReady(fd_set *fdset, int totalnumclients, int sock) { int cnt, len; char buffer[NCCARGS]; D_PRINTF( "Beginning accept loop\n" ); for (cnt = 0; cnt < totalnumclients; 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); /* Time out if any of the clients are unable to connect to us. */ 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"); } CLEAR_SOCK_ERR; if (BADSOCKET(socknum[cnt] = accept(sock, NULL, 0))) { abort_clients(); errexit("accept() error: %s", neterrstr()); } FD_SET(socknum[cnt], fdset); D_PRINTF( "GetReady(): cnt=%d, socket=%d\n", cnt, socknum[cnt] ); } /* * WAIT FOR A READY. */ fprintf(stdout,"Waiting for READY from %d clients\n",totalnumclients); fflush(stdout); for (cnt = 0; cnt < totalnumclients; cnt++) { 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(); errexit("Received bad READY string: len %d, value %s\n", len,buffer); } D_PRINTF("Got ready from client #%d\n", cnt); } fprintf(stdout,"All READYs received\n"); fflush(stdout); } /* * Start all the clients by sending them a GO message. * totalnumclients is the total number of clients * socknum is an array with the socket descriptors for all the client * connections. We know they are all valid descriptors because we wouldn't * have gotten this far if they weren't. */ void SendGo(int totalnumclients, int *socknum) { int cnt; fprintf(stdout, "Sending GO to all clients\n"); for(cnt = 0; cnt < totalnumclients; cnt++) { 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(fd_set *fdset, page_stats_t **page_stats, time_t *endtime, char *timestr, int totalnumclients, stats_t statarray[]) { fd_set leftfdset; 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); /* * LOOP UNTIL ALL CLIENTS HAVE REPORTED */ for(cnt = 0; cnt < totalnumclients; cnt++) { /* * Send "GO" to each socket and receive its data. */ if(!BADSOCKET(socknum[cnt]) && (FD_ISSET(socknum[cnt],&leftfdset))) { int len; D_PRINTF( "Sending GO to receive data from client %d\n", cnt ); if (NETWRITE(socknum[cnt], GOSTR, GOSTRLEN) != GOSTRLEN) { abort_clients(); errexit("Error sending GO to client %d: %s\n", cnt, neterrstr()); } len = SIZEOF_STATSTEXTBASE + number_of_pages*SIZEOF_DOUBLETEXT; returnval = recvdata(socknum[cnt], stats_as_text, len); if (returnval < 0) { D_PRINTF( "Error reading timing stats: %s\n", neterrstr() ); fprintf(stderr, "Error reading timing stats: %s\nSocket number %d\n", neterrstr(),socknum[cnt]); fprintf(stderr, "recvdata() returned %d\n", returnval); abort_clients(); errexit(""); } /* convert text to stats */ stats_as_text[len] = 0; /* add an end marker */ statarray[cnt] = *text_to_stats(stats_as_text); fputc('.', stdout); /* PROGRESS MARKER */ fflush(stdout); if (randomize) { 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); if (returnval < 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()); fprintf(stderr, "recvdata() returned %d\n", returnval); abort_clients(); errexit(""); } page_stats_as_text[returnval] = 0; /* add end marker */ page_stats[cnt][i] = *text_to_page_stats(page_stats_as_text); } /* end for */ } FD_CLR(socknum[cnt],&leftfdset); NETCLOSE(socknum[cnt]); D_PRINTF("Finished with socket %d\n", socknum[cnt]); socknum[cnt] = BADSOCKET_VALUE; } /* end if socknum */ } /* end for cnt */ /* * 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_t **page_stats, time_t endtime, char *timestr, int totalnumclients, stats_t statarray[], page_stats_t *page_stats_total) { stats_t masterstat; int cnt, i, j; double thruput; struct timeval dtime; /* * Print everything out. */ stats_init(&masterstat); masterstat.rs.totalconnects = 0; for(cnt = 0; cnt < totalnumclients; cnt++) { if(statarray[cnt].rs.totalconnects == 0) { fprintf(stdout, "No connects from client %d. Is the Web server running?\n", cnt); } else if(statarray[cnt].rs.totalconnects > 0) { if(dumpall) { fprintf(stdout,"----------------------------------\n"); 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: %.0lf bytes/sec\n", thruput); } 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; fflush(stdout); } if(masterstat.rs.totalconnects == 0) { fprintf(stdout, "No connects at all. Is the Web server running?\n"); return; } 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; } } } /* * If the -v (verbose) option was used then print even more. Note that * using the -v option can cause a LOT of data to be output and may fill * up disks if you're saving all stdout and stderr to a log file. It may * also greatly increase the run time while all the data is dumped to * screen. */ 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 # %lu\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 %s results:\n", VERSION); fprintf(stdout, "Total number of clients: %5d\n", totalnumclients); if (testtime) { fprintf(stdout, "Test time: %5d minutes\n", testtime); fprintf(stdout, "Server connection rate: %10.2lf connections/sec\n", (double)(masterstat.rs.totalconnects)/(60*testtime)); fprintf(stdout, "Server error rate: %10.2lf err/sec\n", (double)(masterstat.rs.totalerrs)/(60*testtime)); fprintf(stdout, "Server thruput: %10.2lf Mbit/sec\n", (double)(8*masterstat.rs.totalbytes)/(60*testtime*1000000)); fprintf(stdout, "Little's Load Factor: %10.2lf \n", (double)(masterstat.rs.totalresponsetime.tv_sec)/ (1000*60*testtime)); } avgtime(&masterstat.rs.totalresponsetime, masterstat.rs.totalconnects, &dtime); fprintf(stdout, "Average response time: %11.3lf sec\n", timevaldouble(&dtime) / 1000); if (masterstat.rs.totalconnects) { fprintf(stdout, "Error Level: %10.2lf %%\n", (double)(100 * masterstat.rs.totalerrs)/(masterstat.rs.totalconnects)); } else { fprintf(stdout, "Error Level: UNDEFINED\n"); fprintf(stderr, "No connections during run; is the Web server running?\n"); } /* so much for the key metrics */ thruput = 8000 * thruputpersec((double)(masterstat.rs.totalbytes), &(masterstat.rs.totalresponsetime)); fprintf(stdout, "Average client thruput: %10.2lf Mbit/sec\n", thruput/1000000); fprintf(stdout, "Sum of client response times: %10.2lf sec\n", timevaldouble(&(masterstat.rs.totalresponsetime)) / 1000); fprintf(stdout, "Total number of pages read: %5ld\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[]) { /* * sync_sock is the socket that clients will use to let us know that * they've started up and are ready to go. */ int sync_sock; int i, 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); if(totalnumclients == 0) { errexit("No work to do!\n"); } /* 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])); GetReady(&fdset, totalnumclients, sync_sock ); NETCLOSE(sync_sock); /* * Send a "GO" message to all webclients so they will start their tests. */ SendGo(totalnumclients, socknum); /* * Wait for all of the webclients to complete. We have a socket * connection to each webclient process or thread so we should get * a reply from each one. The reply will be the timing information. */ 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. The clients have * their own timers and will contact us when they are finished with * their tests and ready to report their data. */ if (testtime) sleep(testtime * 60); GetResults(&fdset, page_stats, &endtime, timestr, totalnumclients, statarray); PrintResults(page_stats, endtime, timestr, totalnumclients, statarray, page_stats_total); exit(0); }