/**************************************************************************** * * * 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 #include #include #ifdef WIN32 #include #include #include #include #include extern int init_gettimeofday(); #else /* not defined(WIN32) */ #include #include #include #include #include #include #include #include #include #include #include #endif /* WIN32 */ #include "sysdep.h" #include "bench.h" #define _BSD_SIGNALS #define INFINITY 100000000 #define DEFAULTWWWPORT 80 #define LOG_FILE "logfile" #define NCCARGS 4096 /* global variables */ int random_seed; int have_random_seed = 0; int amclient = 0; int havewebserver = 0; int haveproxyserver = 0; int savefile = 0; NETPORT portnum = DEFAULTWWWPORT; int timeexpired = 0; int debug = DEBUG_OFF; long int number_of_pages = 0; char webmaster[MAXHOSTNAMELEN]; char webserver[MAXHOSTNAMELEN]; char proxyserver[MAXHOSTNAMELEN]; char debug_filename[MAXPATHLEN]; THREAD FILE *debugfile = stderr; THREAD FILE *logfile; THREAD stats_t timestat; THREAD rqst_timer_t timerarray[MAXNUMOFFILES]; THREAD SOCKET mastersock = BADSOCKET_VALUE; /* connection to webmaster */ page_list_t *load_file_list; /* actually a dynamic array */ THREAD page_stats_t *page_stats; /* actually a dynamic array */ long int numfiles = 0; long int numargs = 0; int testtime = 0; int numloops = 0; int numclients = 0; int record_all_transactions = 0; int uil_filelist_f = 0; /* filedescriptor of URLs to fetch? */ int randomize = 0; /* use weights to choose UILs? */ int verbose = 0; int total_weight; char uil_filelist[NCCARGS]; char filelist[MAXNUMOFFILES][MAXPATHLEN]; char connectstr[MAXHOSTNAMELEN+10]; #ifdef WIN32 HANDLE hSemaphore; int CounterSemaphore = 0; /* counter semaphore for children */ #endif /* WIN32 */ static void ClientThread(void *); /* used to bypass DNS/YP name resolution for every page */ struct hostent webserv_phe, webmast_phe; struct protoent webserv_ppe, webmast_ppe; unsigned long webserv_addr, webmast_addr; short webserv_type, webmast_type; /* socket type */ /* End of globals */ static void usage(const char *progname) { returnerr("Usage: %s [-w webserver] [-p port_num]\n", progname); returnerr("\t[-c masterhost:port] [-t run_time | -l loops]\n"); returnerr("\t[-n numclients] [-S random_seed] [-u uilfile | url ...]\n"); returnerr("\t[-d | -T ] [-D debugfile] [-v] [-P] [-R]\n"); errexit("\n"); } void alarmhandler(int sig) { /* * UNIX: this is the alarm (SIGALRM) handler called when our timer * expires. Our makeload() and get() loops check this variable to * determine when to stop testing. * NT: the "parent" (e.g. first created) thread of this task is acting * as our timer. When test time is over then this "parent" thread * will call alarmhandler(), thus setting this variable. Threads * doing the file retrieving will check this "timeexpired" variable * each time through their makeload() and get() loops and will stop * if it is set to 1. */ timeexpired = 1; } #ifndef WIN32 static void childhandler(int sig) { int status; /* * We received a signal (SIGCLD or SIGCHLD) that a child process has * died. This is normal when the test is finishing so we will just * do nothing. If a child dies while the test is running then the * webmaster will catch that when it is collecting results. * * printf() is not re-entrant so we can't call it from the signal * handler. */ #ifdef HAVE_WAIT3 while (wait3(&status, WNOHANG, (struct rusage *)0) >= 0) #else while(waitpid(-1, &status, WNOHANG) >= 0) #endif ; /* do nothing */ } #endif /* WIN32 */ /* look up the host name and protocol * called once by main() since all threads * use the same protocol and address */ int resolve_addrs(char *host, char *protocol, struct hostent *host_phe, struct protoent *proto_ppe, unsigned long *addr, short *type) { struct hostent *phe; struct protoent *ppe; /* if IP address given, convert to internal form */ if (isdigit(host[0])) { *addr = inet_addr(host); if (*addr == INADDR_NONE) return(returnerr("Invalid IP address %s\n", host)); } else { /* look up by name */ CLEAR_SOCK_ERR; phe = gethostbyname(host); if (phe == NULL) { D_PRINTF( "Gethostbyname failed: %s", neterrstr() ); return(returnerr("Can't get %s host entry\n", host)); } memcpy(host_phe, phe, sizeof(struct hostent)); memcpy((char *)addr, phe->h_addr, sizeof(*addr)); } /* Map protocol name to protocol number */ CLEAR_SOCK_ERR; ppe = getprotobyname(protocol); if (ppe == 0) { D_PRINTF( "protobyname returned %d\n", ppe ); return(returnerr("Can't get %s protocol entry\n",protocol)); } memcpy(proto_ppe, ppe, sizeof(struct protoent)); *type = SOCK_STREAM; return 0; } /* connect to a socket given the hostname and protocol */ SOCKET connectsock(char *host, NETPORT portnum, char *protocol) { struct sockaddr_in sin; /* an Internet endpoint address */ SOCKET s; /* socket descriptor */ int type; /* socket type */ short proto; int returnval; /* temporary return value */ D_PRINTF( "Beginning connectsock; host=%s port=%d proto=%s\n", host, portnum, protocol ); sin.sin_family = AF_INET; memset((char *)&sin, 0, sizeof(sin)); sin.sin_port = htons(portnum); /* get the contact information */ if (strcmp(host, webserver) == 0) { sin.sin_addr.S_ADDR = webserv_addr; sin.sin_family = PF_INET; proto = webserv_ppe.p_proto; type = webserv_type; } else if (strcmp(host, webmaster) == 0) { sin.sin_addr.S_ADDR = webmast_addr; sin.sin_family = PF_INET; proto = webmast_ppe.p_proto; type = webmast_type; } else { struct hostent host_phe; struct protoent host_ppe; unsigned long host_addr; short host_type; /* socket type */ if (resolve_addrs(host, "tcp", &host_phe, &host_ppe, &host_addr, &host_type)) return returnerr("Can't resolve hostname %s in get()\n", host); sin.sin_addr.S_ADDR = host_addr; sin.sin_family = PF_INET; proto = host_ppe.p_proto; type = host_type; } /* Allocate a socket */ CLEAR_SOCK_ERR; s = socket(PF_INET, type, proto); if (BADSOCKET(s)) { D_PRINTF( "Can't create socket: %s\n",neterrstr() ); return BADSOCKET_VALUE; } /* Connect the socket */ D_PRINTF( "connectsock() Address is family %d, port %d, addr %s\n", sin.sin_family, ntohs(sin.sin_port), inet_ntoa(sin.sin_addr) ); CLEAR_SOCK_ERR; returnval = connect(s, (struct sockaddr *)&sin, sizeof(sin)); if (SOCK_ERR(returnval)) { D_PRINTF( "connect() failed: return=%d, %s\n", returnval, neterrstr() ); NETCLOSE(s); return BADSOCKET_VALUE; } /* all done, returning socket descriptor */ D_PRINTF( "Returning socket %d from connectsock()\n", s ); return(s); } SOCKET connecttomaster(char *str) { char *tempch; SOCKET sock; char msg[100]; char ConnectStr[100]; /* Fix to handle multiple threads */ int tries; TRACE("connecttomaster() entering\n"); strcpy(ConnectStr, str); /* * Break the string into a hostname or IP-address and a port number. */ if((tempch = strpbrk(ConnectStr,":")) == NULL) { /* * Correct format is HOSTNAME:PORT or HOST-IP:PORT */ D_PRINTF( "Incorrect format %s: use hostname:port or ip_addr:port\n", ConnectStr ); returnerr("Incorrect format %s: use host:port or ip_addr:port\n", ConnectStr); return BADSOCKET_VALUE; } /* * Zero out the colon so we have two strings, the hostname (or host * IP address) and the port. */ *tempch = '\0'; tempch++; /* loop here to connect to webmaster - TCP/IP allows no more than 5 * connection requests outstanding at once and thus the webmaster may * reject a connection if there are a lot of client processes */ for (tries = 0; tries < MAX_MASTER_CONNECT_TRIES; tries++) { sock = connectsock(ConnectStr,(NETPORT)atoi(tempch),"tcp"); if (!BADSOCKET(sock)) break; sleep(CONNECT_TRY_DELAY_SEC); } if (BADSOCKET(sock)) { returnerr("Could not connect to master process\n"); return BADSOCKET_VALUE; } /* * Send a message to the webmaster that we are ready to proceed. * When all webclients have reported "ready" then the webmaster will * send a "GO" message to all webclients. */ if (NETWRITE(sock,READYSTR,READYSTRLEN) != READYSTRLEN) { returnerr("Error sending READY message to master"); return BADSOCKET_VALUE; } memset(msg, 0, GOSTRLEN+1); if (NETREAD(sock, msg, GOSTRLEN) != GOSTRLEN) { D_PRINTF( "Error receiving GO message from master: %s\n", neterrstr()); returnerr("Error receiving GO message from master\n"); return BADSOCKET_VALUE; } if (strncmp(GOSTR, msg, GOSTRLEN)) { returnerr("Received non-GO message %s\n",msg); return BADSOCKET_VALUE; } TRACE("connecttomaster(): okay, returning socket %d\n", sock); return sock; } static void accumstats(rqst_timer_t *rqsttimer, page_stats_t *pagestats, stats_t *timestat) { rqst_stats_t rqststats; /* * This part is all debugging info */ D_PRINTF( "Total bytes read: %d \t Body size read: %d\n", rqsttimer->totalbytes, rqsttimer->bodybytes ); D_PRINTF( "Enter time: %10u:%10u \t Exit Time: %10u:%10u\n", rqsttimer->entertime.tv_sec, rqsttimer->entertime.tv_usec, rqsttimer->exittime.tv_sec, rqsttimer->exittime.tv_usec ); D_PRINTF( "Before connect: %10u:%10u \t After connect: %10u:%10u\n", rqsttimer->beforeconnect.tv_sec, rqsttimer->beforeconnect.tv_usec, rqsttimer->afterconnect.tv_sec, rqsttimer->afterconnect.tv_usec ); D_PRINTF( "Before header: %10u:%10u \t After header: %10u:%10u\n", rqsttimer->beforeheader.tv_sec, rqsttimer->beforeheader.tv_usec, rqsttimer->afterheader.tv_sec, rqsttimer->afterheader.tv_usec ); D_PRINTF( "After body: %10u:%10u\n", rqsttimer->afterbody.tv_sec, rqsttimer->afterbody.tv_usec ); /* * Update statistics. */ rqstat_times(&(rqststats), rqsttimer); rqstat_sum(&(timestat->rs), &(rqststats)); rqstat_sum(&(pagestats->rs), &(rqststats)); timestat->page_numbers[rqsttimer->page_number]++; } /* * fetch the set of files that constitute a page * * maxcount -- The number of files in the WWW page. This will always be 1 * since we don't currently support grouping multiple files into * one "page". * pageval -- The number of the WWW page (offset in load_file_list[]) * If -1 then get all files for this "page". * * returns the number of files retrieved */ static int makeload(int maxcount, int pageval) { int cnt; int returnval; page_stats_t page_stats_tmp; char server[MAXHOSTNAMELEN]; NETPORT loc_portnum; TRACE("makeload(): entering: maxcount %d, pageval %d)\n",maxcount,pageval); strcpy(server, webserver); /* Put in default value */ page_stats_init(&page_stats_tmp); D_PRINTF( "Page stats initialized\n" ); for (cnt = 0; cnt < maxcount && !timeexpired; cnt++) { D_PRINTF( "Loop count %d in makeload()\n", cnt ); if (pageval == -1) pageval = cnt; /* check for a filename */ if (strlen(load_file_list[pageval].filename[cnt]) < 1) { D_PRINTF( "Bad filename at pageval %d, count %d\n", pageval, cnt ); return(returnerr("Bad filename at pageval %d, count %d\n", pageval, cnt)); } if (load_file_list[pageval].port_number[cnt] != 0) loc_portnum = load_file_list[pageval].port_number[cnt]; else loc_portnum = portnum; if ((load_file_list[pageval].servername[cnt] != NULL) && *load_file_list[pageval].servername[cnt]) { D_PRINTF( "Copying URL server %s to server\n", load_file_list[pageval].servername[cnt] ); strcpy(server, load_file_list[pageval].servername[cnt]); } if (haveproxyserver) { D_PRINTF( "Copying proxy %s to webserver\n", proxyserver ); strcpy(server, proxyserver); } D_PRINTF( "Calling get(%s, %d, %s, &(timearray[%d]))\n", server, loc_portnum, load_file_list[pageval].filename[cnt], cnt ); returnval = get(server, loc_portnum, load_file_list[pageval].filename[cnt], &(timerarray[cnt])); if (returnval < 0) D_PRINTF( "***GET() RETURNED AN ERROR\n" ); /* * If get() returned a valid time then update statistics. */ if ((returnval == 0) && (timerarray[cnt].valid == 2)) { timerarray[cnt].page_number = pageval; accumstats(&timerarray[cnt], &page_stats_tmp, ×tat); } else if (!timeexpired) /* update error count */ { D_PRINTF( "GET error counter incremented\n" ); timestat.rs.totalerrs++; } if (amclient) { fd_set readfds; struct timeval timeout; int rv; timeout.tv_sec = 0; timeout.tv_usec = 0; FD_ZERO(&readfds); FD_SET(mastersock, &readfds); /* if the webmaster has aborted, quit */ /*XXX: this doesn't seem to work for NT clients */ D_PRINTF("Checking if webmaster has aborted.\n"); rv = select(FD_SETSIZE, &readfds, NULL, NULL, &timeout); if (rv < 0) { D_PRINTF("Client terminating at request of webmaster\n"); exit(2); } } } /* * At this point we have retrieved all files for this "page". The current * version of WebStone doesn't support multiple files per page but some * parts of the program act as if it does. The total time for this page * is the accumulation of times for each file that make up this page. * Update our statistics with this page time. */ if ((returnval == 0) && (cnt == load_file_list[pageval].num_of_files) && (timerarray[cnt-1].valid == 2)) { rqst_stats_t *ps_rs; rqst_stats_t *pst_rs; ps_rs = &(page_stats[pageval].rs); pst_rs = &(page_stats_tmp.rs); rqstat_sum(ps_rs, pst_rs); page_stats[pageval].totalpages++; if (page_stats[pageval].page_size == 0) page_stats[pageval].page_size = (unsigned) page_stats_tmp.rs.totalbody; } D_PRINTF( "\nMakeload output page %d: %d errors, %d pages\n", pageval, timestat.rs.totalerrs, page_stats[pageval].totalpages ); TRACE( "makeload(): returning %d\n", cnt ); return cnt; } #ifdef WIN32 /* close socket library at exit() time */ void sock_cleanup(void) { WSACleanup(); } #endif /* WIN32 */ /* * Command line parsing */ static void ParseCmdLine(int argc, char **argv ) { int getoptch; int currarg; extern char *optarg; extern int optind; int i; while((getoptch = getopt(argc,argv,"c:l:n:p:P:R:S:t:u:U:w:dD:sTv")) != EOF) { switch(getoptch) { case 'c': amclient = 1; sprintf(connectstr, "%s", optarg); break; case 'd': debug = DEBUG_ALL; break; case 'D': sprintf(debug_filename, "%s", optarg); break; case 'l': numloops = atoi(optarg); break; case 'n': numclients = atoi(optarg); break; case 'p': portnum = atoi(optarg); break; case 'P': haveproxyserver = 1; sprintf(proxyserver, "%s", optarg); break; case 'u': case 'U': sprintf(uil_filelist, "%s", optarg); uil_filelist_f = 1; break; case 'R': record_all_transactions = 1; break; case 's': savefile = 1; break; case 'S': random_seed = atoi(optarg); have_random_seed = 1; break; case 't': testtime = 60 * atoi(optarg); break; case 'T': debug |= DEBUG_TRACE; break; case 'v': verbose = 1; break; case 'w': havewebserver = 1; sprintf(webserver,"%s",optarg); break; default: usage(argv[0]); } } returnerr("Client begins...\n"); D_PRINTF( "webclient main(): Running in debug mode.\n" ); TRACE("webclient main(): Running in tracing mode.\n"); /* print the command line */ for (i = 0; i < argc; i++) D_PRINTF( "%s ", argv[i] ); D_PRINTF( "\n\n" ); if(testtime && numloops) usage(argv[0]); /* Either numloops or testtime, but not both. */ /* * There must always be a web-server specified */ if(havewebserver != 1) { returnerr("No WWW Server specified\n"); usage(argv[0]); } currarg = optind; numargs = 0; while(currarg != argc) { /* * get the urls to retrieve. */ if (numargs == MAXNUMOFFILES) { returnerr("Maximum of %d files on the command line.\n"); usage(argv[0]); } sscanf(argv[currarg], "%s", filelist[numargs]); numargs++; currarg++; } if ((numargs != 0) && uil_filelist_f) { returnerr("UILs specified both in a file and on the command line.\n"); usage(argv[0]); } if((numloops == 0) && (testtime == 0)) usage(argv[0]); if(numclients > MAXPROCSPERNODE || numclients < 1) { returnerr("Number of Clients must be between 1 and %d\n", MAXPROCSPERNODE); exit(1); } } void main(int argc, char *argv[]) { long fcount = 0; int i; char *tempch; int err; FILE *fp; #ifndef WIN32 debugfile = stderr; #else WSADATA WSAData; MessageBeep(~0U); /* announce our existence */ MessageBeep(~0U); MessageBeep(~0U); err = WSAStartup(MAKEWORD(1,1), &WSAData); if (err != 0) errexit("Error in WSAStartup()\n"); atexit(sock_cleanup); SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST); init_gettimeofday(); /* create semaphore in locked state */ hSemaphore = CreateSemaphore(NULL, 0, 1, NULL); if(hSemaphore == NULL) errexit("Create semaphore failed: %d", GetLastError()); #endif /* WIN32 */ memset(webserver, 0, sizeof(webserver)); memset(webmaster, 0, sizeof(webmaster)); memset(proxyserver, 0, sizeof(proxyserver)); memset(connectstr, 0, sizeof(connectstr)); ParseCmdLine(argc, argv); /* allow use of IP address */ if(amclient) { if((tempch = strpbrk(connectstr,":")) == NULL) { /* * Correct format is HOSTNAME:PORT or HOST-IP:PORT */ D_PRINTF( "Incorrect format %s: use hostname:port or ip_addr:port\n", connectstr ); returnerr("Incorrect format %s: use host:port or ip_addr:port\n", connectstr); exit(1); } else strncpy(webmaster, connectstr, tempch-connectstr); if(resolve_addrs(webmaster, "tcp", &webmast_phe, &webmast_ppe, &webmast_addr, &webmast_type)) exit(1); } if (haveproxyserver) { D_PRINTF( "Copying proxy %s to webserver\n", proxyserver ); strcpy(webserver, proxyserver); } if (resolve_addrs(webserver, "tcp", &webserv_phe, &webserv_ppe, &webserv_addr, &webserv_type)) exit(1); /* * Initialize filelist data */ load_file_list = (page_list_t *) mymalloc((MAXNUMOFPAGES)*sizeof(page_list_t)); /* Get UILs from a file */ if (uil_filelist_f) { D_PRINTF("Reading filelist from file %s\n", uil_filelist); /* take a guess at the number of URLs in the file */ D_PRINTF( "webclient(): About to parse filelist %s\n", uil_filelist ); D_PRINTF( "Parsing file list: %s\n", uil_filelist ); errno = 0; fp = fopen(uil_filelist, "r"); if (fp == NULL) errexit("Error %d opening filelist: %s\n", errno, strerror(errno)); parse_file_list(fp, load_file_list, &number_of_pages, &numfiles); D_PRINTF( "Setting up weighting for %ld pages\n", number_of_pages ); total_weight = load_percent(load_file_list, number_of_pages); } /* Use UILs from the command line */ else if (numargs > 0) { D_PRINTF("Using filelist from the command line %s\n", uil_filelist); number_of_pages = numargs; /* Already scanned by ParseCmdLine() */ } /* Try to read the filelist from stdin */ else { D_PRINTF("Reading filelist from stdin.\n"); parse_file_list(stdin, load_file_list, &number_of_pages, &numfiles); if (number_of_pages) { D_PRINTF( "Setting up weighting for %ld pages\n", number_of_pages ); total_weight = load_percent(load_file_list, number_of_pages); } } if (number_of_pages < 1) { D_PRINTF( "No valid URLs found\n" ); errexit("No valid URLs found\n"); } /* * We're ready to start the client threads/processes. Tell the * webmaster. */ if (amclient) { /* * Tell the webmaster how many pages we'll use */ printf("%10d", number_of_pages); printf("%10d", randomize); printf("%s", OKSTR); /* sent back to webmaster */ fflush(stdout); } #ifndef WIN32 /* * If we are to fork additional clients on this machine, * we must do it before we connect to the master. */ signal(SIGCHLD, childhandler); for(i = 0; i < numclients; i++) { switch(fork()) { case 0: /* * CHILD */ numclients = 1; ClientThread((void *) random_seed); exit(0); break; case -1: /* * ERROR */ errexit("Error forking child processes\n"); exit(1); default: /* * PARENT */ /* * If the -S command line option was used then we were given * a seed for the random number generator. If each of our * children used the same seed then they'd request the same * sequence of pages and that is undesireable so we increment * the seed before forking each child. */ if (have_random_seed) random_seed++; break; } } /* * Wait for all children to exit. */ while(1) { int pid = wait((int*)0); if ((pid == -1) && errno == ECHILD) break; } #else /* defined(WIN32) */ /* start threads on NT */ for (i = 0; i < numclients; i++) { if (_beginthread(ClientThread, 0, (void *) random_seed) == -1) errexit("_beginthread failed: %d", GetLastError()); random_seed++; } /* * At this point the webmaster has told all webchildren to go ahead and * start running. So now we want to synchronize all sibling webchildren * that were started by a specific rexec() on this machine. * * The reason for this is because Win32 doesn't have UNIX-style signals * so we can't use the alarm()/SIGALRM timing mechanism to tell the * webchildren threads when to stop. Instead, the "parent" that spawned * the webchildren threads for this machine will act as the timer. It * does this by sleeping for a while and then setting the "timeexpired" * variable. As the children fetch web pages from the web server, they * keep checking the "timeexpired" variable. When its set to 1 that means * time is up and they should quit. This semaphore is to ensure that all * the webchildren of a parent start at the same time so that they all * run for the full duration of time as measured by their parent. */ while (CounterSemaphore < numclients) sleep(1); CounterSemaphore = 0; /* start all children simultaneously */ ReleaseSemaphore(hSemaphore, 1, NULL); if (testtime) { sleep(testtime); alarmhandler(0); /* signal end of test to threads */ } /* * Wait for all threads to exit. */ while (CounterSemaphore < numclients) sleep(1); CloseHandle(hSemaphore); #endif /* WIN32 */ return; } void ClientThread(void *thread_arg) { int loopcnt = 0; int filecnt; int loop; int ran_number; int page_index; int page_number; int file_count = 0; char file_name[50]; struct timeval runningtime; int i; int returnval; unsigned int my_random_seed; /* * INITIALIZE DATA */ page_stats = (page_stats_t *) mymalloc((number_of_pages)*sizeof(page_stats_t)); for (i=0; i < number_of_pages; i++) page_stats_init(&(page_stats[i])); if (DEBUGGING_ANY) { /* * If we are running in debug mode (the "-d" command-line option) * then we will write a lot of debugging info (the D_PRINTF() lines) * into a file. */ fflush(stderr); if (strlen(debug_filename)) { sprintf(file_name, "%s.%d", debug_filename, (int)getpid()); debugfile = fopen(file_name, "w+"); if (debugfile == NULL) errexit("Can't open debug file\n"); } D_PRINTF( "Running in debug mode, %d\n",amclient ); } TRACE("ClientThread(): starting\n"); if (record_all_transactions) { /* * If the -R option was given then we'll record all timing data * in a log file. */ sprintf(file_name, "%s%d", LOG_FILE, (int)getpid()); returnerr("Log file is %s\n", file_name); logfile = fopen(file_name, "w+"); } /* * Initialize random number generator. If the "-S seed" command-line * arguments were specified then use "seed" to initialized the random * number generator. We can use the same seed every time to produce * the same sequence of "random" numbers, thus making test results more * reproducible. */ my_random_seed = (have_random_seed ? (unsigned int) thread_arg : getpid()); D_PRINTF( "Random seed: %d\n", my_random_seed ); SRANDOM(my_random_seed); /* Do more initializations. */ for (i=0; i < MAXNUMOFFILES; i++) rqtimer_init(&(timerarray[i])); stats_init(×tat); D_PRINTF( "Number of files %d\n", numfiles ); timestat.total_num_of_files = numfiles; if (amclient) { /* * We are a webclient process or thread started by the webmaster. * Connect back to the webmaster to tell it we're ready to go. */ D_PRINTF( "Trying to connect with %s\n",connectstr ); mastersock = connecttomaster(connectstr); if(BADSOCKET(mastersock)) errexit("Error connecting to the master: %s\n", neterrstr()); } #ifdef WIN32 /* Tell parent we're ready */ InterlockedIncrement(&CounterSemaphore); /* Wait for main() thread to release us */ WaitForSingleObject(hSemaphore, INFINITE); ReleaseSemaphore(hSemaphore, 1, NULL); #else if (testtime != 0) { /* * Unix webclients will just keep looping, fetching pages from the * web server until the alarm goes off. Since Win32 doesn't have * signals it uses a different stopping mechanism. */ signal(SIGALRM, alarmhandler); alarm(testtime); } #endif /* WIN32 */ /* * And they're off... */ if (testtime) numloops = INFINITY; GETTIMEOFDAY(&(timestat.starttime), &(timestat.starttimezone)); /* * start fetching pages from the web server until we either hit a * specific number of loops or else our timer goes off. * * If we are running a timed test: since our INFINITY is really a * finite number, this assumes that we won't loop that many times * in whatever time period we've allotted. */ for(loopcnt = 0; (loopcnt < numloops) && !timeexpired; loopcnt++) { if (randomize) { if (testtime != 0) { D_PRINTF( "Running in timed mode\n" ); /* random number between 0 and totalweight-1 */ ran_number = (RANDOM() % total_weight); D_PRINTF( "random %ld\n", ran_number ); /* loop through pages, find correct one * while ran_number is positive, decrement it * by the load_num of the current page * example: ran_number is 5, pages have weights * of 10 and 10 * first iteration page_index = 0, * ran_number = -5 * iteration halted, page_index = 0 */ page_index = -1; while (ran_number >= 0) { page_index++; D_PRINTF( "Current page index %d: %ld - %d\n", page_index, ran_number, load_file_list[page_index].load_num); ran_number -= load_file_list[page_index].load_num; } if (page_index >= number_of_pages) page_index--; D_PRINTF( "Final page index %d\n", page_index ); filecnt = makeload(load_file_list[page_index].num_of_files, page_index); } else /* NOT RUNNING IN TIMED MODE */ { for (page_number = 0; page_number < number_of_pages; page_number++) { filecnt = makeload(load_file_list[page_number].num_of_files, page_number); } } } else { /* * We don't have a filelist, but we do have a set of file URLs. * Passing -1 to makeload means get the entire set of files. */ D_PRINTF( "No filelist\n" ); filecnt = makeload(numfiles, -1); } if (filecnt > 0) file_count += filecnt; } GETTIMEOFDAY(&(timestat.endtime), &(timestat.endtimezone)); TRACE( "ClientThread(): test run complete\n" ); signal(SIGALRM, NULL); if (testtime == 0) { numfiles = loopcnt; if (!numargs) numfiles = file_count; } if (record_all_transactions) { /* * Dump the log file information. */ TRACE("ClientThread(): doing record_all_transactions dump.\n"); for (loop=0; loop < (loopcnt * file_count); loop++) { fprintf(logfile, " entertime \t%d.%d\n" " beforeconnect \t%d.%d\n" " afterconnect \t%d.%d\n" " beforeheader \t%d.%d\n" " afterheader \t%d.%d\n" " afterbody \t%d.%d\n" " exittime \t%d.%d\n" " total bytes \t%d\n" " body bytes\t%d\n", timerarray[loop].entertime.tv_sec, timerarray[loop].entertime.tv_usec, timerarray[loop].beforeconnect.tv_sec, timerarray[loop].beforeconnect.tv_usec, timerarray[loop].afterconnect.tv_sec, timerarray[loop].afterconnect.tv_usec, timerarray[loop].beforeheader.tv_sec, timerarray[loop].beforeheader.tv_usec, timerarray[loop].afterheader.tv_sec, timerarray[loop].afterheader.tv_usec, timerarray[loop].afterbody.tv_sec, timerarray[loop].afterbody.tv_usec, timerarray[loop].exittime.tv_sec, timerarray[loop].exittime.tv_usec, timerarray[loop].totalbytes, timerarray[loop].bodybytes); } /* end for loop */ } /* end if recording all transactions */ D_PRINTF( "total errors: %d\n",timestat.rs.totalerrs ); D_PRINTF( "Server is: %s running at port number: %d\n", webserver,portnum ); if (amclient) /* True if we were started by the webmaster process */ { int rv; char *stats_as_text; char msg[100]; /* * Wait until the webmaster program is ready to receive our data */ memset(msg, 0, GOSTRLEN+1); if (rv=NETREAD(mastersock, msg, GOSTRLEN) != GOSTRLEN) { D_PRINTF( "Error receiving GO message from master: %s\n", neterrstr()); D_PRINTF( "Received %d characters, <%s>\n", rv, msg); errexit("Error receiving GO message from master\n"); } if (strncmp(GOSTR, msg, GOSTRLEN)) errexit("Received non-GO message %s\n",msg); /* * Send the timing data to the webmaster */ TRACE("ClientThread(): sending timing data to webmaster.\n"); stats_as_text = stats_to_text(×tat); D_PRINTF( "stats_to_text returned %s\n", stats_as_text ); returnval = senddata(mastersock, stats_as_text, SIZEOF_STATSTEXTBASE + number_of_pages*SIZEOF_DOUBLETEXT); D_PRINTF( "Wrote time stats to master %d\n", returnval ); if (returnval < 1) errexit("Error while writing time stats: %s\n", neterrstr()); if (randomize) /* write pagestats */ { char *page_stats_as_text; for (i = 0; i < number_of_pages; i++) { TRACE( "ClientThread(): on page_stats[%d]\n", i ); page_stats_as_text = page_stats_to_text(&page_stats[i]); returnval = strlen(page_stats_as_text); D_PRINTF("page_stats_to_text[%d] returned %d\n", i, returnval ); returnval = senddata(mastersock, page_stats_as_text, SIZEOF_PAGESTATSTEXT); if (returnval < 1) { D_PRINTF( "Error while writing page_stats[%d]: %s\n", i, neterrstr() ); errexit("Error while writing page_stats[%d]: %s\n", i, neterrstr()); } /* end if */ D_PRINTF( "Wrote %d bytes of page_stats[%d] to master\n", returnval, i ); } } D_PRINTF( "About to close socket\n" ); if (NETCLOSE(mastersock)) D_PRINTF( "Close socket error: %s\n", neterrstr() ); } else /* We weren't started by a webmaster process */ { if (testtime) printf("Test ran for: %d minutes\n",(testtime/60)); else printf("Test ran for: %d iterations.\n",numloops); compdifftime(&(timestat.endtime), &(timestat.starttime), &(runningtime)); /* printf("Total time of test (sec) %d.%d\n", runningtime.tv_sec, runningtime.tv_usec); */ printf("Total time of test (sec) %.6lf\n", timevaldouble(&runningtime)); printf("Files retrieved per iteration: %d\n",numfiles); printf("----------------------------------\n"); printf("Totals:\n\n"); rqstat_print(&(timestat.rs)); if (timestat.rs.totalconnects == 0) goto end; printf("Thruput = %1.0lf bytes/sec\n", thruputpersec(timestat.rs.totalbytes, &runningtime)); if (numloops && verbose) { for (loop = 0; loop < number_of_pages; loop++) { if (timestat.page_numbers[loop] != 0) { printf ("===============================================================================\n"); printf ("Page # %d\n\n", loop); printf ("Total number of times page was hit %d\n", page_stats[loop].totalpages); rqstat_print(&(page_stats[loop].rs)); printf ("Page size %d \n", page_stats[loop].page_size); printf ("===============================================================================\n\n"); } } } } end: if (record_all_transactions) fclose(logfile); if (DEBUGGING_ANY) { TRACE( "ClientThread(): client exiting.\n" ); fclose(debugfile); } #ifdef WIN32 /* tell parent we're done */ InterlockedIncrement(&CounterSemaphore); #endif /* WIN32 */ }