1 /****************************************************************************
2  *                                                                          *
3  * The contents of this file are subject to the WebStone Public License     *
4  * Version 1.0 (the "License"); you may not use this file except in         *
5  * compliance with the License. You may obtain a copy of the License        *
6  * at http://www.mindcraft.com/webstone/license10.html                      *
7  *                                                                          *
8  * Software distributed under the License is distributed on an "AS IS"      *
9  * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See      *
10  * the License for the specific language governing rights and limitations   *
11  * under the License.                                                       *
12  *                                                                          *
13  * The Original Code is WebStone 2.5.                                       *
14  *                                                                          *
15  * The Initial Developer of the Original Code is Silicon Graphics, Inc.     *
16  * and Mindcraft, Inc.. Portions created by Silicon Graphics. and           *
17  * Mindcraft. are Copyright (C) 1995-1998 Silicon Graphics, Inc. and        *
18  * Mindcraft, Inc. All Rights Reserved.                                     *
19  *                                                                          *
20  * Contributor(s): ______________________________________.                  *
21  *                                                                          *
22  * @(#) bench.c 2.4@(#)                                                     *
23  ***************************************************************************/
24 
25 
26 #include <stdio.h>
27 #include <errno.h>
28 #include <signal.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <time.h>
32 #include <math.h>
33 #include <sys/types.h>
34 #include <ctype.h>
35 
36 #ifdef WIN32
37 #include <windows.h>
38 #include <winsock.h>
39 #include <time.h>
40 #include <process.h>
41 #include <io.h>
42 extern int init_gettimeofday();
43 #else /* not defined(WIN32) */
44 #include <netdb.h>
45 #include <unistd.h>
46 #include <sys/param.h>
47 #include <sys/ipc.h>
48 #include <sys/shm.h>
49 #include <sys/errno.h>
50 #include <sys/socket.h>
51 #include <sys/time.h>
52 #include <sys/wait.h>
53 #include <netinet/in.h>
54 #include <arpa/inet.h>
55 #endif /* WIN32 */
56 
57 #include "sysdep.h"
58 #include "bench.h"
59 
60 #define _BSD_SIGNALS
61 #define INFINITY	100000000
62 #define DEFAULTWWWPORT	80
63 #define LOG_FILE	"logfile"
64 #define NCCARGS 	4096
65 
66 /* global variables */
67 int	random_seed;
68 int	have_random_seed = 0;
69 int	amclient = 0;
70 int	havewebserver = 0;
71 int	haveproxyserver = 0;
72 int	savefile = 0;
73 NETPORT	portnum = DEFAULTWWWPORT;
74 int	timeexpired = 0;
75 int	debug = DEBUG_OFF;
76 long int	number_of_pages = 0;
77 char	webmaster[MAXHOSTNAMELEN];
78 char	webserver[MAXHOSTNAMELEN];
79 char	proxyserver[MAXHOSTNAMELEN];
80 char	debug_filename[MAXPATHLEN];
81 
82 THREAD FILE	*debugfile;
83 THREAD FILE	*logfile;
84 THREAD stats_t	timestat;
85 THREAD rqst_timer_t	timerarray[MAXNUMOFFILES];
86 THREAD SOCKET	mastersock = BADSOCKET_VALUE;	/* connection to webmaster */
87 
88 page_list_t	*load_file_list;		/* actually a dynamic array */
89 THREAD page_stats_t	*page_stats;	/* actually a dynamic array */
90 
91 long int	numfiles = 0;
92 long int	numargs = 0;
93 int			testtime = 0;
94 int			numloops = 0;
95 int			numclients = 0;
96 int			record_all_transactions = 0;
97 int			uil_filelist_f = 0; /* filedescriptor of URLs to fetch? */
98 int			randomize = 0;		/* use weights to choose UILs? */
99 int			verbose = 0;
100 int			total_weight;
101 char		uil_filelist[NCCARGS];
102 char		filelist[MAXNUMOFFILES][MAXPATHLEN];
103 char		connectstr[MAXHOSTNAMELEN+10];
104 
105 
106 #ifdef WIN32
107 HANDLE	hSemaphore;
108 int	CounterSemaphore = 0;		/* counter semaphore for children */
109 #endif /* WIN32 */
110 
111 static void ClientThread(void *);
112 
113 /* used to bypass DNS/YP name resolution for every page */
114 struct hostent	webserv_phe, webmast_phe;
115 struct protoent	webserv_ppe, webmast_ppe;
116 unsigned long	webserv_addr, webmast_addr;
117 short		webserv_type, webmast_type;               /* socket type */
118 
119 /* End of globals */
120 
121 
122 static void
usage(const char * progname)123 usage(const char *progname)
124 {
125 	returnerr("Usage: %s [-w webserver] [-p port_num]\n", progname);
126 	returnerr("\t[-c masterhost:port]  [-t run_time | -l loops]\n");
127 	returnerr("\t[-n numclients] [-S random_seed] [-u uilfile | url ...]\n");
128 	returnerr("\t[-d | -T ] [-D debugfile] [-v] [-P] [-R]\n");
129 	errexit("\n");
130 }
131 
132 
133 void
alarmhandler(int sig)134 alarmhandler(int sig)
135 {
136 	/*
137 	 * UNIX: this is the alarm (SIGALRM) handler called when our timer
138 	 * 		 expires.  Our makeload() and get() loops check this variable to
139 	 *		 determine when to stop testing.
140 	 * NT:	 the "parent" (e.g. first created) thread of this task is acting
141 	 *	     as our timer.  When test time is over then this "parent" thread
142      *		 will call alarmhandler(), thus setting this variable.  Threads
143 	 *	     doing the file retrieving will check this "timeexpired" variable
144 	 *		 each time through their makeload() and get() loops and will stop
145 	 *		 if it is set to 1.
146 	 */
147 	timeexpired = 1;
148 }
149 
150 #ifndef WIN32
151 static void
childhandler(int sig)152 childhandler(int sig)
153 {
154 	int status;
155 
156 	/*
157 	 * We received a signal (SIGCLD or SIGCHLD) that a child process has
158 	 * died.  This is normal when the test is finishing so we will just
159 	 * do nothing.  If a child dies while the test is running then the
160 	 * webmaster will catch that when it is collecting results.
161 	 *
162 	 * printf() is not re-entrant so we can't call it from the signal
163 	 * handler.
164 	 */
165 #ifdef HAVE_WAIT3
166 	while (wait3(&status, WNOHANG, (struct rusage *)0) >= 0)
167 #else
168 	while(waitpid(-1, &status, WNOHANG) >= 0)
169 #endif
170 		; /* do nothing */
171 }
172 #endif /* WIN32 */
173 
174 
175 /* look up the host name and protocol
176  * called once by main() since all threads
177  * use the same protocol and address
178  */
179 int
resolve_addrs(char * host,char * protocol,struct hostent * host_phe,struct protoent * proto_ppe,unsigned long * addr,short * type)180 resolve_addrs(char *host, char *protocol, struct hostent *host_phe,
181 		struct protoent *proto_ppe, unsigned long *addr,
182 		short *type)
183 {
184 	struct hostent *phe;
185 	struct protoent *ppe;
186 
187 	/* if IP address given, convert to internal form */
188 	if (isdigit(host[0]))
189 	{
190 		*addr = inet_addr(host);
191 		if (*addr == INADDR_NONE)
192 		    return(returnerr("Invalid IP address %s\n", host));
193 	}
194 	else
195 	{
196 		/* look up by name */
197 		CLEAR_SOCK_ERR;
198 		phe = gethostbyname(host);
199 		if (phe == NULL)
200 		{
201 			D_PRINTF( "Gethostbyname failed: %s", neterrstr() );
202 			return(returnerr("Can't get %s host entry\n", host));
203 		}
204 		memcpy(host_phe, phe, sizeof(struct hostent));
205 		memcpy((char *)addr, phe->h_addr, sizeof(*addr));
206 	}
207 
208 	/* Map protocol name to protocol number */
209 	CLEAR_SOCK_ERR;
210 	ppe = getprotobyname(protocol);
211 	if (ppe == 0)
212 	{
213 		D_PRINTF( "protobyname returned %d\n",	ppe );
214 		return(returnerr("Can't get %s protocol entry\n",protocol));
215 	}
216 	memcpy(proto_ppe, ppe, sizeof(struct protoent));
217 
218 	*type = SOCK_STREAM;
219 	return 0;
220 }
221 
222 
223 /* connect to a socket given the hostname and protocol */
224 SOCKET
connectsock(char * host,NETPORT portnum,char * protocol)225 connectsock(char *host, NETPORT portnum, char *protocol)
226 {
227 	struct sockaddr_in sin; /* an Internet endpoint address */
228 	SOCKET 	s;              /* socket descriptor */
229 	int 	type;           /* socket type */
230 	short 	proto;
231 	int 	returnval;	/* temporary return value */
232 
233 	D_PRINTF( "Beginning connectsock; host=%s port=%d proto=%s\n", host,
234 			portnum, protocol );
235 
236 	sin.sin_family = AF_INET;
237 	memset((char *)&sin, 0, sizeof(sin));
238 
239 	sin.sin_port = htons(portnum);
240 
241 	/* get the contact information */
242 	if (strcmp(host, webserver) == 0)
243 	{
244 		sin.sin_addr.S_ADDR = webserv_addr;
245 		sin.sin_family = PF_INET;
246 		proto = webserv_ppe.p_proto;
247 		type = webserv_type;
248 	}
249 	else if (strcmp(host, webmaster) == 0)
250 	{
251 		sin.sin_addr.S_ADDR = webmast_addr;
252 		sin.sin_family = PF_INET;
253 		proto = webmast_ppe.p_proto;
254 		type = webmast_type;
255 	}
256 	else
257 	{
258 		struct hostent 	host_phe;
259 		struct protoent	host_ppe;
260 		unsigned long	host_addr;
261 		short 			host_type;       /* socket type */
262 
263 		if (resolve_addrs(host, "tcp", &host_phe, &host_ppe, &host_addr,
264 						  &host_type))
265 			return returnerr("Can't resolve hostname %s in get()\n", host);
266 		sin.sin_addr.S_ADDR = host_addr;
267 		sin.sin_family = PF_INET;
268 		proto = host_ppe.p_proto;
269 		type = host_type;
270 	}
271 
272 	/* Allocate a socket */
273 	CLEAR_SOCK_ERR;
274 	s = socket(PF_INET, type, proto);
275 	if (BADSOCKET(s))
276 	{
277 		D_PRINTF( "Can't create socket: %s\n",neterrstr() );
278 		return BADSOCKET_VALUE;
279 	}
280 
281 	/* Connect the socket */
282 	D_PRINTF( "connectsock() Address is family %d, port %d, addr %s\n",
283 			sin.sin_family, ntohs(sin.sin_port), inet_ntoa(sin.sin_addr) );
284 
285 	CLEAR_SOCK_ERR;
286 	returnval = connect(s, (struct sockaddr *)&sin, sizeof(sin));
287 	if (SOCK_ERR(returnval))
288 	{
289 		D_PRINTF( "connect() failed: return=%d, %s\n", returnval, neterrstr() );
290 		NETCLOSE(s);
291 		return BADSOCKET_VALUE;
292 	}
293 
294 	/* all done, returning socket descriptor */
295 	D_PRINTF( "Returning socket %d from connectsock()\n", s );
296 	return(s);
297 }
298 
299 
300 SOCKET
connecttomaster(char * str)301 connecttomaster(char *str)
302 {
303 	char   	*tempch;
304 	SOCKET	sock;
305 	char	msg[100];
306 	char 	ConnectStr[100];	/* Fix to handle multiple threads */
307 	int		tries;
308 
309 	TRACE("connecttomaster() entering\n");
310 	strcpy(ConnectStr, str);
311 
312 	/*
313 	 * Break the string into a hostname or IP-address and a port number.
314 	 */
315 	if((tempch = strpbrk(ConnectStr,":")) == NULL)
316 	{
317 		/*
318 		 * Correct format is HOSTNAME:PORT or HOST-IP:PORT
319 		 */
320 		D_PRINTF( "Incorrect format %s: use hostname:port or ip_addr:port\n",
321 				ConnectStr );
322 		returnerr("Incorrect format %s: use host:port or ip_addr:port\n",
323 						ConnectStr);
324 		return BADSOCKET_VALUE;
325 	}
326 
327 	/*
328 	 * Zero out the colon so we have two strings, the hostname (or host
329  	 * IP address) and the port.
330 	 */
331 	*tempch = '\0';
332 	tempch++;
333 
334 	/* loop here to connect to webmaster - TCP/IP allows no more than 5
335 	 * connection requests outstanding at once and thus the webmaster may
336 	 * reject a connection if there are a lot of client processes
337 	 */
338 	for (tries = 0; tries < MAX_MASTER_CONNECT_TRIES; tries++)
339 	{
340 		sock = connectsock(ConnectStr,(NETPORT)atoi(tempch),"tcp");
341 		if (!BADSOCKET(sock))
342 			break;
343 		sleep(CONNECT_TRY_DELAY_SEC);
344 	}
345 	if (BADSOCKET(sock))
346 	{
347 		returnerr("Could not connect to master process\n");
348 		return BADSOCKET_VALUE;
349 	}
350 
351 	/*
352 	 * Send a message to the webmaster that we are ready to proceed.
353 	 * When all webclients have reported "ready" then the webmaster will
354 	 * send a "GO" message to all webclients.
355 	 */
356 	if (NETWRITE(sock,READYSTR,READYSTRLEN) != READYSTRLEN)
357 	{
358 		returnerr("Error sending READY message to master");
359 		return BADSOCKET_VALUE;
360 	}
361 
362 	memset(msg, 0, GOSTRLEN+1);
363 	if (NETREAD(sock, msg, GOSTRLEN) != GOSTRLEN)
364 	{
365 		D_PRINTF( "Error receiving GO message from master: %s\n", neterrstr());
366 		returnerr("Error receiving GO message from master\n");
367 		return BADSOCKET_VALUE;
368 	}
369 
370 	if (strncmp(GOSTR, msg, GOSTRLEN))
371 	{
372 		returnerr("Received non-GO message %s\n",msg);
373 		return BADSOCKET_VALUE;
374 	}
375 
376 	TRACE("connecttomaster(): okay, returning socket %d\n", sock);
377 	return sock;
378 }
379 
380 
381 static void
accumstats(rqst_timer_t * rqsttimer,page_stats_t * pagestats,stats_t * timestat)382 accumstats(rqst_timer_t *rqsttimer, page_stats_t *pagestats, stats_t *timestat)
383 {
384 	rqst_stats_t	rqststats;
385 
386 	/*
387 	 * This part is all debugging info
388 	 */
389 	D_PRINTF( "Total bytes read: %d \t Body size read: %d\n",
390 		rqsttimer->totalbytes, rqsttimer->bodybytes );
391 
392 	D_PRINTF( "Enter time:     %10u:%10u \t Exit Time:     %10u:%10u\n",
393 		rqsttimer->entertime.tv_sec,
394 		rqsttimer->entertime.tv_usec,
395 		rqsttimer->exittime.tv_sec,
396 		rqsttimer->exittime.tv_usec );
397 	D_PRINTF( "Before connect: %10u:%10u \t After connect: %10u:%10u\n",
398 		rqsttimer->beforeconnect.tv_sec,
399 		rqsttimer->beforeconnect.tv_usec,
400 		rqsttimer->afterconnect.tv_sec,
401 		rqsttimer->afterconnect.tv_usec );
402 	D_PRINTF( "Before header:  %10u:%10u \t After header:  %10u:%10u\n",
403 		rqsttimer->beforeheader.tv_sec,
404 		rqsttimer->beforeheader.tv_usec,
405 		rqsttimer->afterheader.tv_sec,
406 		rqsttimer->afterheader.tv_usec );
407 	D_PRINTF( "After body:     %10u:%10u\n",
408 		rqsttimer->afterbody.tv_sec,
409 		rqsttimer->afterbody.tv_usec );
410 
411 	/*
412 	 * Update statistics.
413 	 */
414 	rqstat_times(&(rqststats), rqsttimer);
415 	rqstat_sum(&(timestat->rs), &(rqststats));
416 	rqstat_sum(&(pagestats->rs), &(rqststats));
417 	timestat->page_numbers[rqsttimer->page_number]++;
418 }
419 
420 
421 /*
422  * fetch the set of files that constitute a page
423  *
424  * maxcount -- The number of files in the WWW page.  This will always be 1
425  *             since we don't currently support grouping multiple files into
426  *             one "page".
427  * pageval  -- The number of the WWW page (offset in load_file_list[])
428  *	           If -1 then get all files for this "page".
429  *
430  * returns the number of files retrieved
431  */
432 static int
makeload(int maxcount,int pageval)433 makeload(int maxcount, int pageval)
434 {
435 	int cnt;
436 	int returnval;
437 	page_stats_t page_stats_tmp;
438 	char server[MAXHOSTNAMELEN];
439 	NETPORT loc_portnum;
440 
441 	TRACE("makeload(): entering: maxcount %d, pageval %d)\n",maxcount,pageval);
442 
443 	strcpy(server, webserver); /* Put in default value */
444 
445 	page_stats_init(&page_stats_tmp);
446 	D_PRINTF( "Page stats initialized\n" );
447 
448 	for (cnt = 0; cnt < maxcount && !timeexpired; cnt++)
449 	{
450 		D_PRINTF( "Loop count %d in makeload()\n", cnt );
451 		if (pageval == -1)
452 		    pageval = cnt;
453 
454 		/* check for a filename */
455 		if (strlen(load_file_list[pageval].filename[cnt]) < 1)
456 		{
457 		    D_PRINTF( "Bad filename at pageval %d, count %d\n", pageval, cnt );
458 		    return(returnerr("Bad filename at pageval %d, count %d\n",
459 							 pageval, cnt));
460 		}
461 
462 		if (load_file_list[pageval].port_number[cnt] != 0)
463 			loc_portnum = load_file_list[pageval].port_number[cnt];
464 		else
465 			loc_portnum = portnum;
466 		if ((load_file_list[pageval].servername[cnt] != NULL)
467 			&& *load_file_list[pageval].servername[cnt])
468 		{
469 			D_PRINTF( "Copying URL server %s to server\n",
470 						load_file_list[pageval].servername[cnt] );
471 			strcpy(server, load_file_list[pageval].servername[cnt]);
472 		}
473 
474 		if (haveproxyserver)
475 		{
476 			D_PRINTF( "Copying proxy %s to webserver\n",
477 						proxyserver );
478 			strcpy(server, proxyserver);
479 		}
480 
481 
482 		D_PRINTF( "Calling get(%s, %d, %s, &(timearray[%d]))\n", server,
483 					loc_portnum, load_file_list[pageval].filename[cnt], cnt );
484 
485 		returnval = get(server, loc_portnum,
486 						load_file_list[pageval].filename[cnt],
487 						&(timerarray[cnt]));
488 		if (returnval < 0)
489 			D_PRINTF( "***GET() RETURNED AN ERROR\n" );
490 
491 		/*
492 		 * If get() returned a valid time then update statistics.
493 		 */
494 		if ((returnval == 0) && (timerarray[cnt].valid == 2))
495 		{
496 			timerarray[cnt].page_number = pageval;
497 			accumstats(&timerarray[cnt], &page_stats_tmp, &timestat);
498 		}
499 		else if (!timeexpired) /* update error count */
500 		{
501 			D_PRINTF( "GET error counter incremented\n" );
502 			timestat.rs.totalerrs++;
503 		}
504 
505 		if (amclient) {
506 			fd_set readfds;
507 			struct timeval timeout;
508 			int rv;
509 
510 			timeout.tv_sec = 0;
511 			timeout.tv_usec = 0;
512 			FD_ZERO(&readfds);
513 			FD_SET(mastersock, &readfds);
514 
515 			/* if the webmaster has aborted, quit */
516 			/*XXX: this doesn't seem to work for NT clients */
517 			D_PRINTF("Checking if webmaster has aborted.\n");
518  			rv = select(FD_SETSIZE, &readfds, NULL, NULL, &timeout);
519  			if (rv < 0)
520 			{
521 				D_PRINTF("Client terminating at request of webmaster\n");
522 				exit(2);
523 			}
524 		}
525 
526 	}
527 
528 	/*
529 	 * At this point we have retrieved all files for this "page".  The current
530  	 * version of WebStone doesn't support multiple files per page but some
531 	 * parts of the program act as if it does.  The total time for this page
532 	 * is the accumulation of times for each file that make up this page.
533 	 * Update our statistics with this page time.
534 	 */
535 	if ((returnval == 0)
536 	    && (cnt == load_file_list[pageval].num_of_files)
537 	    && (timerarray[cnt-1].valid == 2))
538 	{
539 		rqst_stats_t *ps_rs;
540 		rqst_stats_t *pst_rs;
541 
542 		ps_rs = &(page_stats[pageval].rs);
543 		pst_rs = &(page_stats_tmp.rs);
544 		rqstat_sum(ps_rs, pst_rs);
545 		page_stats[pageval].totalpages++;
546 
547 		if (page_stats[pageval].page_size == 0)
548 			page_stats[pageval].page_size = (unsigned)
549 											page_stats_tmp.rs.totalbody;
550 	}
551 
552 	D_PRINTF( "\nMakeload output page %d: %d errors, %d pages\n",
553 	    	pageval, timestat.rs.totalerrs, page_stats[pageval].totalpages );
554 	TRACE( "makeload(): returning %d\n", cnt );
555 	return cnt;
556 }
557 
558 #ifdef WIN32
559 /* close socket library at exit() time */
560 void
sock_cleanup(void)561 sock_cleanup(void)
562 {
563     WSACleanup();
564 }
565 #endif /* WIN32 */
566 
567 /*
568  * Command line parsing
569  */
570 static void
ParseCmdLine(int argc,char ** argv)571 ParseCmdLine(int argc, char **argv )
572 {
573     int     getoptch;
574     int     currarg;
575     extern char *optarg;
576     extern int  optind;
577 	int		i;
578 
579 	while((getoptch = getopt(argc,argv,"c:l:n:p:P:R:S:t:u:U:w:dD:sTv")) != EOF)
580 	{
581 		switch(getoptch)
582 		{
583 		case 'c':
584 		    amclient = 1;
585 		    sprintf(connectstr, "%s", optarg);
586 		    break;
587 		case 'd':
588 		    debug = DEBUG_ALL;
589 		    break;
590 		case 'D':
591 		    sprintf(debug_filename, "%s", optarg);
592 		    break;
593 		case 'l':
594 		    numloops = atoi(optarg);
595 		    break;
596 		case 'n':
597 		    numclients = atoi(optarg);
598 		    break;
599 		case 'p':
600 		    portnum = atoi(optarg);
601 		    break;
602 		case 'P':
603 		    haveproxyserver = 1;
604 		    sprintf(proxyserver, "%s", optarg);
605 		    break;
606 		case 'u':
607 		case 'U':
608 		    sprintf(uil_filelist, "%s", optarg);
609 		    uil_filelist_f = 1;
610 		    break;
611 		case 'R':
612 		    record_all_transactions = 1;
613 		    break;
614 		case 's':
615 		    savefile = 1;
616 		    break;
617 		case 'S':
618 		    random_seed = atoi(optarg);
619 			have_random_seed = 1;
620 		    break;
621 		case 't':
622 		    testtime = 60 * atoi(optarg);
623 		    break;
624 		case 'T':
625 			debug |= DEBUG_TRACE;
626 		    break;
627 		case 'v':
628 		    verbose = 1;
629 		    break;
630 		case 'w':
631 		    havewebserver = 1;
632 		    sprintf(webserver,"%s",optarg);
633 		    break;
634 		default:
635 		    usage(argv[0]);
636 		}
637 	}
638 
639 	returnerr("Client begins...\n");
640 	D_PRINTF( "webclient main(): Running in debug mode.\n" );
641 	TRACE("webclient main(): Running in tracing mode.\n");
642 
643 	/* print the command line */
644 	for (i = 0; i < argc; i++)
645 		D_PRINTF( "%s ", argv[i] );
646 	D_PRINTF( "\n\n" );
647 
648 	if(testtime && numloops)
649 		usage(argv[0]); /* Either numloops or testtime, but not both.  */
650 
651 	/*
652  	 * There must always be a web-server specified
653 	 */
654 	if(havewebserver != 1)
655 	{
656 		returnerr("No WWW Server specified\n");
657 		usage(argv[0]);
658 	}
659 
660 	currarg = optind;
661 	numargs = 0;
662 	while(currarg != argc)
663 	{
664 		/*
665 		 * get the urls to retrieve.
666 		 */
667 		if (numargs == MAXNUMOFFILES)
668 		{
669 			returnerr("Maximum of %d files on the command line.\n");
670 			usage(argv[0]);
671 		}
672 		sscanf(argv[currarg], "%s", filelist[numargs]);
673 		numargs++;
674 		currarg++;
675 	}
676 
677 	if ((numargs != 0) && uil_filelist_f)
678 	{
679 		returnerr("UILs specified both in a file and on the command line.\n");
680 		usage(argv[0]);
681 	}
682 
683 	if((numloops == 0) && (testtime == 0))
684 		usage(argv[0]);
685 
686 	if(numclients > MAXPROCSPERNODE || numclients < 1)
687 	{
688 		returnerr("Number of Clients must be between 1 and %d\n",
689 					MAXPROCSPERNODE);
690 		exit(1);
691 	}
692 }
693 
694 int
main(int argc,char * argv[])695 main(int argc, char *argv[])
696 {
697 	long	fcount = 0;
698 	int 	i;
699 	char 	*tempch;
700 	int 	err;
701 	FILE	*fp;
702 
703 #ifndef WIN32
704 	debugfile = stderr;
705 #else
706 	WSADATA 	WSAData;
707 
708 	MessageBeep(~0U);	/* announce our existence */
709 	MessageBeep(~0U);
710 	MessageBeep(~0U);
711 
712 	err = WSAStartup(MAKEWORD(1,1), &WSAData);
713 	if (err != 0)
714 		errexit("Error in WSAStartup()\n");
715 
716 	atexit(sock_cleanup);
717 
718 	SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
719 
720 	init_gettimeofday();
721 
722 	/* create semaphore in locked state */
723 	hSemaphore = CreateSemaphore(NULL, 0, 1, NULL);
724 	if(hSemaphore == NULL)
725 		errexit("Create semaphore failed: %d", GetLastError());
726 #endif /* WIN32 */
727 
728 	memset(webserver, 0, sizeof(webserver));
729 	memset(webmaster, 0, sizeof(webmaster));
730 	memset(proxyserver, 0, sizeof(proxyserver));
731 	memset(connectstr, 0, sizeof(connectstr));
732 
733 	ParseCmdLine(argc, argv);
734 
735 
736 	/* allow use of IP address */
737 	if(amclient)
738 	{
739 		if((tempch = strpbrk(connectstr,":")) == NULL)
740 		{
741 			/*
742 			 * Correct format is HOSTNAME:PORT or HOST-IP:PORT
743 			 */
744 			D_PRINTF( "Incorrect format %s: use hostname:port or ip_addr:port\n", connectstr );
745 			returnerr("Incorrect format %s: use host:port or ip_addr:port\n",
746 						connectstr);
747 			exit(1);
748 		}
749 		else
750 			strncpy(webmaster, connectstr, tempch-connectstr);
751 		if(resolve_addrs(webmaster, "tcp", &webmast_phe, &webmast_ppe,
752 				&webmast_addr, &webmast_type))
753 			exit(1);
754 	}
755 
756 	if (haveproxyserver)
757 	{
758 		D_PRINTF( "Copying proxy %s to webserver\n", proxyserver );
759 		strcpy(webserver, proxyserver);
760 	}
761 
762 	if (resolve_addrs(webserver, "tcp", &webserv_phe, &webserv_ppe,
763 				&webserv_addr, &webserv_type))
764 		exit(1);
765 
766 	/*
767 	 * Initialize filelist data
768 	 */
769 	load_file_list = (page_list_t *)
770 					mymalloc((MAXNUMOFPAGES)*sizeof(page_list_t));
771 
772 	/* Get UILs from a file */
773 	if (uil_filelist_f)
774 	{
775 		D_PRINTF("Reading filelist from file %s\n", uil_filelist);
776 
777 		/* take a guess at the number of URLs in the file */
778 		D_PRINTF( "webclient(): About to parse filelist %s\n", uil_filelist );
779 
780 		D_PRINTF( "Parsing file list: %s\n", uil_filelist );
781 		errno = 0;
782 		fp = fopen(uil_filelist, "r");
783 		if (fp == NULL)
784 			errexit("Error %d opening filelist: %s\n", errno, strerror(errno));
785 
786 		parse_file_list(fp, load_file_list, &number_of_pages, &numfiles);
787 
788 		D_PRINTF( "Setting up weighting for %ld pages\n", number_of_pages );
789 		total_weight = load_percent(load_file_list, number_of_pages);
790 	}
791 
792 	/* Use UILs from the command line */
793 	else if (numargs > 0)
794 	{
795 		D_PRINTF("Using filelist from the command line %s\n", uil_filelist);
796 		number_of_pages = numargs;	/* Already scanned by ParseCmdLine() */
797 	}
798 
799 	/* Try to read the filelist from stdin */
800 	else
801 	{
802 		D_PRINTF("Reading filelist from stdin.\n");
803 
804 		parse_file_list(stdin, load_file_list, &number_of_pages, &numfiles);
805 		if (number_of_pages)
806 		{
807 			D_PRINTF( "Setting up weighting for %ld pages\n", number_of_pages );
808 			total_weight = load_percent(load_file_list, number_of_pages);
809 		}
810 	}
811 
812 	if (number_of_pages < 1)
813 	{
814 		D_PRINTF( "No valid URLs found\n" );
815 		errexit("No valid URLs found\n");
816 	}
817 
818 	/*
819 	 * We're ready to start the client threads/processes.  Tell the
820      * webmaster.
821 	 */
822 	if (amclient)
823 	{
824 		/*
825 		 * Tell the webmaster how many pages we'll use
826 		 */
827 		printf("%10d", number_of_pages);
828 
829 		printf("%10d", randomize);
830 
831 		printf("%s", OKSTR);     /* sent back to webmaster */
832 		fflush(stdout);
833 	}
834 
835 #ifndef WIN32
836 	/*
837 	 * If we are to fork additional clients on this machine,
838 	 * we must do it before we connect to the master.
839 	 */
840 	signal(SIGCHLD, childhandler);
841 	for(i = 0; i < numclients; i++)
842 	{
843 		switch(fork())
844 		{
845 		case 0:
846 			/*
847 			 * CHILD
848 			 */
849 			numclients = 1;
850 			ClientThread((void *) random_seed);
851 			exit(0);
852 			break;
853 		case -1:
854 			/*
855 			 * ERROR
856 			 */
857 			errexit("Error forking child processes\n");
858 			exit(1);
859 		default:
860 			/*
861 			 * PARENT
862 			 */
863 			/*
864 			 * If the -S command line option was used then we were given
865 			 * a seed for the random number generator.  If each of our
866 			 * children used the same seed then they'd request the same
867 			 * sequence of pages and that is undesireable so we increment
868 			 * the seed before forking each child.
869 			 */
870 			if (have_random_seed)
871 				random_seed++;
872 			break;
873 		}
874 	}
875 
876 	/*
877 	 * Wait for all children to exit.
878 	 */
879 	while(1)
880 	{
881 		int pid = wait((int*)0);
882 		if ((pid == -1) && errno == ECHILD)
883 			break;
884 	}
885 #else /* defined(WIN32) */
886 	/* start threads on NT */
887 	for (i = 0; i < numclients; i++)
888 	{
889 		if (_beginthread(ClientThread, 0, (void *) random_seed) == -1)
890 			errexit("_beginthread failed: %d", GetLastError());
891 		random_seed++;
892 	}
893 	/*
894  	 * At this point the webmaster has told all webchildren to go ahead and
895 	 * start running.  So now we want to synchronize all sibling webchildren
896 	 * that were started by a specific rexec() on this machine.
897 	 *
898 	 * The reason for this is because Win32 doesn't have UNIX-style signals
899  	 * so we can't use the alarm()/SIGALRM timing mechanism to tell the
900 	 * webchildren threads when to stop.  Instead, the "parent" that spawned
901 	 * the webchildren threads for this machine will act as the timer.  It
902 	 * does this by sleeping for a while and then setting the "timeexpired"
903 	 * variable.  As the children fetch web pages from the web server, they
904 	 * keep checking the "timeexpired" variable.  When its set to 1 that means
905 	 * time is up and they should quit.  This semaphore is to ensure that all
906 	 * the webchildren of a parent start at the same time so that they all
907 	 * run for the full duration of time as measured by their parent.
908 	 */
909 	while (CounterSemaphore < numclients)
910 		sleep(1);
911 	CounterSemaphore = 0;
912 
913 	/* start all children simultaneously */
914 	ReleaseSemaphore(hSemaphore, 1, NULL);
915 
916 	if (testtime)
917 	{
918 		sleep(testtime);
919 		alarmhandler(0);	/* signal end of test to threads */
920 	}
921 
922 	/*
923 	 * Wait for all threads to exit.
924 	 */
925 	while (CounterSemaphore < numclients)
926 		sleep(1);
927 
928 	CloseHandle(hSemaphore);
929 #endif /* WIN32 */
930 
931 	return(0);
932 }
933 
934 void
ClientThread(void * thread_arg)935 ClientThread(void *thread_arg)
936 {
937 	int		loopcnt = 0;
938 	int		filecnt;
939 	int		loop;
940 	int		ran_number;
941 	int		page_index;
942 	int		page_number;
943 	int		file_count = 0;
944 	char	file_name[50];
945 	struct timeval	runningtime;
946 	int		i;
947 	int		returnval;
948 	unsigned int my_random_seed;
949 
950 	/*
951 	 * INITIALIZE DATA
952 	 */
953 
954 	page_stats = (page_stats_t *)
955 					mymalloc((number_of_pages)*sizeof(page_stats_t));
956 
957 	for (i=0; i < number_of_pages; i++)
958 		page_stats_init(&(page_stats[i]));
959 
960 	if (DEBUGGING_ANY)
961 	{
962 		/*
963 		 * If we are running in debug mode (the "-d" command-line option)
964 		 * then we will write a lot of debugging info (the D_PRINTF() lines)
965 		 * into a file.
966 		 */
967 		fflush(stderr);
968 		if (strlen(debug_filename))
969 		{
970 			sprintf(file_name, "%s.%d", debug_filename, (int)getpid());
971 			debugfile = fopen(file_name, "w+");
972 			if (debugfile == NULL)
973 				errexit("Can't open debug file\n");
974 		}
975 		D_PRINTF( "Running in debug mode, %d\n",amclient );
976 	}
977 	TRACE("ClientThread(): starting\n");
978 
979 	if (record_all_transactions)
980 	{
981 		/*
982 		 * If the -R option was given then we'll record all timing data
983 		 * in a log file.
984 		 */
985 		sprintf(file_name, "%s%d", LOG_FILE, (int)getpid());
986 		returnerr("Log file is %s\n", file_name);
987 		logfile = fopen(file_name, "w+");
988 	}
989 
990 	/*
991 	 * Initialize random number generator.  If the "-S seed" command-line
992 	 * arguments were specified then use "seed" to initialized the random
993 	 * number generator.  We can use the same seed every time to produce
994 	 * the same sequence of "random" numbers, thus making test results more
995 	 * reproducible.
996 	 */
997 	my_random_seed = (have_random_seed ? (unsigned int) thread_arg : getpid());
998 	D_PRINTF( "Random seed: %d\n", my_random_seed );
999 	SRANDOM(my_random_seed);
1000 
1001 	/* Do more initializations. */
1002 	for (i=0; i < MAXNUMOFFILES; i++)
1003 		rqtimer_init(&(timerarray[i]));
1004 	stats_init(&timestat);
1005 
1006 	D_PRINTF( "Number of files %d\n", numfiles );
1007 	timestat.total_num_of_files = numfiles;
1008 
1009 	if (amclient)
1010 	{
1011 		/*
1012 		 * We are a webclient process or thread started by the webmaster.
1013 		 * Connect back to the webmaster to tell it we're ready to go.
1014 		 */
1015 		D_PRINTF( "Trying to connect with %s\n",connectstr );
1016 		mastersock = connecttomaster(connectstr);
1017 		if(BADSOCKET(mastersock))
1018 			errexit("Error connecting to the master: %s\n", neterrstr());
1019 	}
1020 
1021 #ifdef WIN32
1022 	/* Tell parent we're ready */
1023 	InterlockedIncrement(&CounterSemaphore);
1024 
1025 	/* Wait for main() thread to release us */
1026 	WaitForSingleObject(hSemaphore, INFINITE);
1027 	ReleaseSemaphore(hSemaphore, 1, NULL);
1028 #else
1029 
1030 	if (testtime != 0)
1031 	{
1032 		/*
1033 		 * Unix webclients will just keep looping, fetching pages from the
1034 		 * web server until the alarm goes off.  Since Win32 doesn't have
1035 		 * signals it uses a different stopping mechanism.
1036 		 */
1037 		signal(SIGALRM, alarmhandler);
1038 		alarm(testtime);
1039 	}
1040 #endif /* WIN32 */
1041 
1042 	/*
1043 	 * And they're off...
1044 	 */
1045 	if (testtime)
1046 		numloops = INFINITY;
1047 	GETTIMEOFDAY(&(timestat.starttime), &(timestat.starttimezone));
1048 
1049 	/*
1050 	 * start fetching pages from the web server until we either hit a
1051 	 * specific number of loops or else our timer goes off.
1052  	 *
1053 	 * If we are running a timed test: since our INFINITY is really a
1054 	 * finite number, this assumes that we won't loop that many times
1055 	 * in whatever time period we've allotted.
1056 	 */
1057 
1058 	for(loopcnt = 0;  (loopcnt < numloops) && !timeexpired;  loopcnt++)
1059 	{
1060 		if (randomize)
1061 		{
1062 			if (testtime != 0)
1063 			{
1064 				D_PRINTF( "Running in timed mode\n" );
1065 
1066 				/* random number between 0 and totalweight-1 */
1067 				ran_number = (RANDOM() % total_weight);
1068 				D_PRINTF( "random %ld\n", ran_number );
1069 
1070 				/* loop through pages, find correct one
1071 				 * while ran_number is positive, decrement it
1072 				 * by the load_num of the current page
1073 				 * example: ran_number is 5, pages have weights
1074 				 * of 10 and 10
1075 				 * first iteration page_index = 0,
1076 				 * ran_number = -5
1077 				 * iteration halted, page_index = 0
1078 				 */
1079 				page_index = -1;
1080 				while (ran_number >= 0)
1081 				{
1082 					page_index++;
1083 					D_PRINTF( "Current page index %d: %ld - %d\n", page_index,
1084 							ran_number, load_file_list[page_index].load_num);
1085 					ran_number -= load_file_list[page_index].load_num;
1086 				}
1087 
1088 				if (page_index >= number_of_pages)
1089 					page_index--;
1090 
1091 				D_PRINTF( "Final page index %d\n", page_index );
1092 				filecnt = makeload(load_file_list[page_index].num_of_files,
1093 								   page_index);
1094 			}
1095 			else /* NOT RUNNING IN TIMED MODE */
1096 			{
1097 				for (page_number = 0;
1098 						page_number < number_of_pages;
1099 							page_number++)
1100 				{
1101 					filecnt = makeload(load_file_list[page_number].num_of_files,
1102 									 	page_number);
1103 
1104 				}
1105 			}
1106 		}
1107 		else
1108 		{
1109 			/*
1110 		 	 * We don't have a filelist, but we do have a set of file URLs.
1111 		 	 * Passing -1 to makeload means get the entire set of files.
1112 			 */
1113 			D_PRINTF( "No filelist\n" );
1114 			filecnt = makeload(numfiles, -1);
1115 		}
1116 		if (filecnt > 0)
1117 			file_count += filecnt;
1118 
1119 	}
1120 
1121 	GETTIMEOFDAY(&(timestat.endtime), &(timestat.endtimezone));
1122 	TRACE( "ClientThread(): test run complete\n" );
1123 	signal(SIGALRM, NULL);
1124 
1125 	if (testtime == 0)
1126 	{
1127 		numfiles = loopcnt;
1128 		if (!numargs)
1129 			numfiles = file_count;
1130 	}
1131 
1132 	if (record_all_transactions)
1133 	{
1134 		/*
1135 		 * Dump the log file information.
1136 		 */
1137 		TRACE("ClientThread(): doing record_all_transactions dump.\n");
1138 		for (loop=0; loop < (loopcnt * file_count); loop++)
1139 		{
1140 			fprintf(logfile, " entertime \t%d.%d\n"
1141 					 " beforeconnect \t%d.%d\n"
1142 					 " afterconnect \t%d.%d\n"
1143 					 " beforeheader \t%d.%d\n"
1144 					 " afterheader \t%d.%d\n"
1145 					 " afterbody \t%d.%d\n"
1146 					 " exittime \t%d.%d\n"
1147 					 " total bytes \t%d\n"
1148 					 " body bytes\t%d\n",
1149 				timerarray[loop].entertime.tv_sec,
1150 				timerarray[loop].entertime.tv_usec,
1151 				timerarray[loop].beforeconnect.tv_sec,
1152 				timerarray[loop].beforeconnect.tv_usec,
1153 				timerarray[loop].afterconnect.tv_sec,
1154 				timerarray[loop].afterconnect.tv_usec,
1155 				timerarray[loop].beforeheader.tv_sec,
1156 				timerarray[loop].beforeheader.tv_usec,
1157 				timerarray[loop].afterheader.tv_sec,
1158 				timerarray[loop].afterheader.tv_usec,
1159 				timerarray[loop].afterbody.tv_sec,
1160 				timerarray[loop].afterbody.tv_usec,
1161 				timerarray[loop].exittime.tv_sec,
1162 				timerarray[loop].exittime.tv_usec,
1163 				timerarray[loop].totalbytes,
1164 				timerarray[loop].bodybytes);
1165 		} /* end for loop */
1166 	} /* end if recording all transactions */
1167 
1168 	D_PRINTF( "total errors: %d\n",timestat.rs.totalerrs );
1169 	D_PRINTF( "Server is: %s running at port number: %d\n",
1170 		webserver,portnum );
1171 
1172 	if (amclient) /* True if we were started by the webmaster process */
1173 	{
1174  		int rv;
1175 		char *stats_as_text;
1176  		char msg[100];
1177 
1178 		/*
1179 		 * Wait until the webmaster program is ready to receive our data
1180 		 */
1181  		memset(msg, 0, GOSTRLEN+1);
1182  		if (rv=NETREAD(mastersock, msg, GOSTRLEN) != GOSTRLEN)
1183  		{
1184  			D_PRINTF( "Error receiving GO message from master: %s\n",
1185  				neterrstr());
1186  			D_PRINTF( "Received %d characters, <%s>\n", rv, msg);
1187  			errexit("Error receiving GO message from master\n");
1188  		}
1189 
1190  		if (strncmp(GOSTR, msg, GOSTRLEN))
1191  			errexit("Received non-GO message %s\n",msg);
1192 
1193 		/*
1194 		 * Send the timing data to the webmaster
1195 		 */
1196 		TRACE("ClientThread(): sending timing data to webmaster.\n");
1197 		stats_as_text = stats_to_text(&timestat);
1198 		D_PRINTF( "stats_to_text returned %s\n", stats_as_text );
1199 
1200 		returnval = senddata(mastersock, stats_as_text,
1201 		SIZEOF_STATSTEXTBASE + number_of_pages*SIZEOF_DOUBLETEXT);
1202 		D_PRINTF( "Wrote time stats to master %d\n", returnval );
1203 		if (returnval < 1)
1204 			errexit("Error while writing time stats: %s\n", neterrstr());
1205 
1206 		if (randomize) /* write pagestats */
1207 		{
1208 			char *page_stats_as_text;
1209 			for (i = 0; i < number_of_pages; i++)
1210 			{
1211 				TRACE( "ClientThread(): on page_stats[%d]\n", i );
1212 				page_stats_as_text = page_stats_to_text(&page_stats[i]);
1213 				returnval = strlen(page_stats_as_text);
1214 				D_PRINTF("page_stats_to_text[%d] returned %d\n", i, returnval );
1215 				returnval = senddata(mastersock, page_stats_as_text,
1216 									SIZEOF_PAGESTATSTEXT);
1217 				if (returnval < 1)
1218 				{
1219 					D_PRINTF( "Error while writing page_stats[%d]: %s\n",
1220 								i, neterrstr() );
1221 					errexit("Error while writing page_stats[%d]: %s\n",
1222 								i, neterrstr());
1223 				} /* end if */
1224 				D_PRINTF( "Wrote %d bytes of page_stats[%d] to master\n",
1225 							returnval, i );
1226 			}
1227 		}
1228 
1229 		D_PRINTF( "About to close socket\n" );
1230 		if (NETCLOSE(mastersock))
1231 			D_PRINTF( "Close socket error: %s\n", neterrstr() );
1232 	}
1233 	else /* We weren't started by a webmaster process */
1234 	{
1235 		if (testtime)
1236 			printf("Test ran for: %d minutes\n",(testtime/60));
1237 		else
1238 			printf("Test ran for: %d iterations.\n",numloops);
1239 		compdifftime(&(timestat.endtime), &(timestat.starttime),
1240 						&(runningtime));
1241 /*
1242 		printf("Total time of test (sec) %d.%d\n", runningtime.tv_sec,
1243 				runningtime.tv_usec);
1244 */
1245 		printf("Total time of test (sec) %.6lf\n",
1246 			timevaldouble(&runningtime));
1247 		printf("Files retrieved per iteration: %d\n",numfiles);
1248 		printf("----------------------------------\n");
1249 		printf("Totals:\n\n");
1250 		rqstat_print(&(timestat.rs));
1251 
1252 		if (timestat.rs.totalconnects == 0)
1253 		    goto end;
1254 		printf("Thruput = %1.0lf bytes/sec\n",
1255 		       thruputpersec(timestat.rs.totalbytes, &runningtime));
1256 
1257 		if (numloops && verbose)
1258 		{
1259 			for (loop = 0; loop < number_of_pages; loop++)
1260 			{
1261 				if (timestat.page_numbers[loop] != 0)
1262 				{
1263 					printf ("===============================================================================\n");
1264 					printf ("Page # %d\n\n", loop);
1265 					printf ("Total number of times page was hit %d\n",
1266 							page_stats[loop].totalpages);
1267 					rqstat_print(&(page_stats[loop].rs));
1268 					printf ("Page size %d \n", page_stats[loop].page_size);
1269 					printf ("===============================================================================\n\n");
1270 				}
1271 			}
1272 		}
1273 	}
1274 
1275 end:
1276 	if (record_all_transactions)
1277 		fclose(logfile);
1278 	if (DEBUGGING_ANY)
1279 	{
1280 		TRACE( "ClientThread(): client exiting.\n" );
1281 		fclose(debugfile);
1282 	}
1283 
1284 #ifdef WIN32
1285 	/* tell parent we're done */
1286 	InterlockedIncrement(&CounterSemaphore);
1287 #endif /* WIN32 */
1288 }
1289