/****************************************************************************
 *                                                                          *
 * 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 <stdio.h>
#include <errno.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <math.h>
#include <sys/types.h>
#include <ctype.h>

#ifdef WIN32
#include <windows.h>
#include <winsock.h>
#include <time.h>
#include <process.h>
#include <io.h>
extern int init_gettimeofday();
#else /* not defined(WIN32) */
#include <netdb.h>
#include <unistd.h>
#include <sys/param.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/errno.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#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] = "";

#ifdef STONE_SSL
#define MAXSSLVERSIONLEN 8
#define MAXSSLCIPHERLEN 32
static char ssl_version[MAXSSLVERSIONLEN];
int ssl_client_cache_mode = 0;
static char ssl_cipher[MAXSSLCIPHERLEN];  
double ssl_mix = 1.0;
double ssl_handshake = 0.0;
NETPORT ssl_portnum = 8443;
#endif

THREAD FILE	*debugfile;
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))
	{
		perror("");
		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 );

#ifdef STONE_SSL
		returnval = get(server, loc_portnum, 
						load_file_list[pageval].filename[cnt],
						&(timerarray[cnt]), ssl_version, ssl_cipher);
#else
		returnval = get(server, loc_portnum, 
						load_file_list[pageval].filename[cnt],
						&(timerarray[cnt]));
#endif
		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, &timestat);
		}
		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;

#ifdef STONE_SSL
	ssl_cipher[0] = '0';
	ssl_version[0] = '0';
#endif

#ifndef STONE_SSL
	while((getoptch = getopt(argc,argv,"c:l:n:p:P:R:S:t:u:U:w:dD:sTv")) != EOF)
#else
	while((getoptch = getopt(argc,argv,"c:C:l:m:n:p:P:r:R:S:t:u:U:w:dD:sTvV:x:y:")) != EOF)
#endif
	{
		switch(getoptch)
		{
		case 'c':
		    amclient = 1;
		    sprintf(connectstr, "%s", optarg);
		    break;
#ifdef STONE_SSL
		case 'C':
			sprintf(ssl_cipher, "%s", optarg);
			fprintf(stderr, "SSL cipher = %s\n", ssl_cipher);
			break;
#endif
		case 'd':
		    debug = DEBUG_ALL;
		    break;
		case 'D':
		    sprintf(debug_filename, "%s", optarg);
		    break;
		case 'l':
		    numloops = atoi(optarg);
		    break;
#ifdef STONE_SSL
		case 'm':
			if(strcasecmp(optarg, "on") == 0)
				ssl_client_cache_mode = 1;
			else
				ssl_client_cache_mode = 0;
			D_PRINTF("SSL client cache mode = %d\n", ssl_client_cache_mode);
			break;
#endif
		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;
#ifdef STONE_SSL
		case 'V':
			sprintf(ssl_version, "%s", optarg);
			D_PRINTF("SSL version = %s\n", ssl_version);
			break;
		case 'r':
			sscanf(optarg, "%lf", &ssl_handshake);
			D_PRINTF("ssl_handshake = %d\n", ssl_handshake);
			break;	
#endif
		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;
#ifdef STONE_SSL
		case 'x':
			sscanf(optarg, "%lf", &ssl_mix);
			D_PRINTF("SSL mix = %lf\n", ssl_mix);
			break;
		case 'y':
			ssl_portnum = atoi(optarg);
			break;
#endif
		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;

	fprintf(stderr, "in main\n");

#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));

	fprintf(stderr, "starting parse\n");
	ParseCmdLine(argc, argv);
	fprintf(stderr, "done parse\n");


	/* 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(&timestat);

	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(&timestat);
		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 */
} 
