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, ×tat);
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(×tat);
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(×tat);
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