xref: /dragonfly/test/stress/webstress/webstress.c (revision 0ca59c34)
1 /*
2  * The software known as "DragonFly" or "DragonFly BSD" is distributed under
3  * the following terms:
4  *
5  * Copyright (c) 2003, 2004, 2005 The DragonFly Project.  All rights reserved.
6  *
7  * This code is derived from software contributed to The DragonFly Project
8  * by Matthew Dillon <dillon@backplane.com>
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  *
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in
18  *    the documentation and/or other materials provided with the
19  *    distribution.
20  * 3. Neither the name of The DragonFly Project nor the names of its
21  *    contributors may be used to endorse or promote products derived
22  *    from this software without specific, prior written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
27  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
28  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
29  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
30  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
31  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
32  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
33  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
34  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35  * SUCH DAMAGE.
36  *
37  * $DragonFly: src/test/stress/webstress/webstress.c,v 1.1 2005/04/05 00:13:20 dillon Exp $
38  */
39 /*
40  * webstress [-n num] [-r] [-f url_file] [-l limit_ms] [-c count] url url
41  *			url...
42  *
43  * Fork N processes (default 8).  Each process makes a series of connections
44  * to retrieve the specified URLs.  Any transaction that takes longer the
45  * limit_ms (default 1000) to perform is reported.
46  *
47  * If the -r option is specified the list of URLs is randomized.
48  *
49  * If the -f option is specified URLs are read from a file.  Multiple -f
50  * options may be specified instead of or in addition to specifying additional
51  * URLs on the command line.
52  *
53  * All URLs should begin with http:// but this is optional.  Only http is
54  * supported.
55  *
56  * By default the program runs until you ^C it.  The -c option may be
57  * used to limit the number of loops.
58  *
59  * WARNING!  This can easily blow out available sockets on the client or
60  * server, or blow out available ports on the client, due to sockets left
61  * in TIME_WAIT.  It is recommended that net.inet.tcp.msl be lowered
62  * considerably to run this test.  The test will abort if the system runs
63  * out of sockets or ports.
64  */
65 
66 #include <sys/types.h>
67 #include <sys/socket.h>
68 #include <sys/wait.h>
69 #include <sys/time.h>
70 #include <netinet/in.h>
71 #include <netinet/tcp.h>
72 #include <arpa/inet.h>
73 
74 #include <stdio.h>
75 #include <stdlib.h>
76 #include <string.h>
77 #include <stdarg.h>
78 #include <unistd.h>
79 #include <errno.h>
80 #include <netdb.h>
81 
82 typedef struct urlnode {
83     struct urlnode *url_next;
84     struct sockaddr_in url_sin;
85     char *url_host;
86     char *url_path;
87 } *urlnode_t;
88 
89 static void usage(void);
90 static void read_url_file(const char *path);
91 static void run_test(urlnode_t *array, int count);
92 static void add_url(const char *url);
93 
94 int fork_opt = 8;
95 int random_opt;
96 int limit_opt = 1000000;
97 int report_interval = 100;
98 int loop_count = 0;
99 urlnode_t url_base;
100 urlnode_t *url_nextp = &url_base;
101 int url_count;
102 
103 int
104 main(int ac, char **av)
105 {
106     int ch;
107     int i;
108     urlnode_t node;
109     urlnode_t *array;
110     pid_t pid;
111 
112     while ((ch = getopt(ac, av, "c:f:l:n:r")) != -1) {
113 	printf("CH %c\n", ch);
114 	switch(ch) {
115 	case 'c':
116 	    loop_count = strtol(optarg, NULL, 0);
117 	    if (report_interval > loop_count)
118 		report_interval = loop_count;
119 	    break;
120 	case 'n':
121 	    fork_opt = strtol(optarg, NULL, 0);
122 	    break;
123 	case 'r':
124 	    random_opt = 1;
125 	    break;
126 	case 'f':
127 	    read_url_file(optarg);
128 	    break;
129 	case 'l':
130 	    limit_opt = strtol(optarg, NULL, 0) * 1000;
131 	    break;
132 	default:
133 	    usage();
134 	    /* not reached */
135 	    break;
136 	}
137     }
138     ac -= optind;
139     av += optind;
140     for (i = 0; i < ac; ++i)
141 	add_url(av[i]);
142     if (url_base == NULL)
143 	usage();
144 
145     /*
146      * Convert the list to an array
147      */
148     array = malloc(sizeof(urlnode_t) * url_count);
149     for (i = 0, node = url_base; i < url_count; ++i, node = node->url_next) {
150 	array[i] = node;
151     }
152 
153     /*
154      * Dump the list
155      */
156     printf("URL LIST:\n");
157     for (node = url_base; node; node = node->url_next) {
158 	printf("    http://%s:%d/%s\n",
159 	    inet_ntoa(node->url_sin.sin_addr),
160 	    ntohs(node->url_sin.sin_port),
161 	    node->url_path
162 	);
163     }
164     printf("Running...\n");
165 
166     /*
167      * Fork children and start the test
168      */
169     for (i = 0; i < fork_opt; ++i) {
170 	if ((pid = fork()) == 0) {
171 	    run_test(array, url_count);
172 	    exit(0);
173 	} else if (pid == (pid_t)-1) {
174 	    printf("unable to fork child %d\n", i);
175 	    exit(1);
176 	}
177     }
178     while (wait(NULL) >= 0 || errno == EINTR)
179 	;
180     return(0);
181 }
182 
183 static
184 void
185 usage(void)
186 {
187     fprintf(stderr,
188 	"%s [-n num] [-r] [-f url_file] [-l limit_ms] [-c loops] url url...\n"
189 	"    -n num        number of forks (8)\n"
190 	"    -r            randomize list (off)\n"
191 	"    -f url_file   read URLs from file\n"
192 	"    -l limit_ms   report if transaction latency >limit (1000)\n"
193 	"    -c loops      test loops (0 == infinite)\n"
194 	"\n"
195  "WARNING!  This can easily blow out available sockets on the client or\n"
196  "server, or blow out available ports on the client, due to sockets left\n"
197  "in TIME_WAIT.  It is recommended that net.inet.tcp.msl be lowered\n"
198  "considerably to run this test.  The test will abort if the system runs\n"
199  "out of sockets or ports.\n",
200 	getprogname()
201     );
202     exit(1);
203 }
204 
205 static
206 void
207 read_url_file(const char *path)
208 {
209     char buf[1024];
210     FILE *fi;
211     int len;
212 
213     if ((fi = fopen(path, "r")) != NULL) {
214 	while (fgets(buf, sizeof(buf), fi) != NULL) {
215 	    if (buf[0] == '#')
216 		continue;
217 	    len = strlen(buf);
218 	    if (len && buf[len-1] == '\n')
219 		buf[len-1] = 0;
220 	    add_url(buf);
221 	}
222 	fclose(fi);
223     } else {
224 	fprintf(stderr, "Unable to open %s\n", path);
225 	exit(1);
226     }
227 }
228 
229 static
230 void
231 add_url(const char *url)
232 {
233     struct hostent *hen;
234     const char *base;
235     const char *ptr;
236     char *hostname;
237     urlnode_t node;
238     int error;
239 
240     node = malloc(sizeof(*node));
241     bzero(node, sizeof(*node));
242 
243     base = url;
244     if (strncmp(url, "http://", 7) == 0)
245 	base += 7;
246     if ((ptr = strchr(base, '/')) == NULL) {
247 	fprintf(stderr, "malformed URL: %s\n", base);
248 	free(node);
249 	return;
250     }
251     hostname = malloc(ptr - base + 1);
252     bcopy(base, hostname, ptr - base);
253     hostname[ptr - base] = 0;
254     base = ptr + 1;
255     if ((ptr = strrchr(hostname, ':')) != NULL) {
256 	*strrchr(hostname, ':') = 0;
257 	++ptr;
258 	node->url_sin.sin_port = htons(strtol(ptr, NULL, 0));
259     } else {
260 	node->url_sin.sin_port = htons(80);
261     }
262     node->url_sin.sin_len = sizeof(node->url_sin);
263     node->url_sin.sin_family = AF_INET;
264     error = inet_aton(hostname, &node->url_sin.sin_addr);
265     if (error < 0) {
266 	fprintf(stderr, "unable to parse host/ip: %s (%s)\n",
267 		hostname, strerror(errno));
268 	free(node);
269 	return;
270     }
271     if (error == 0) {
272 	if ((hen = gethostbyname(hostname)) == NULL) {
273 	    fprintf(stderr, "unable to resolve host: %s (%s)\n",
274 		hostname, hstrerror(h_errno));
275 	    free(node);
276 	    return;
277 	}
278 	bcopy(hen->h_addr, &node->url_sin.sin_addr, hen->h_length);
279 	node->url_sin.sin_family = hen->h_addrtype;
280     }
281     node->url_host = strdup(hostname);
282     node->url_path = strdup(base);
283     *url_nextp = node;
284     url_nextp = &node->url_next;
285     ++url_count;
286 }
287 
288 static
289 void
290 run_test(urlnode_t *array, int count)
291 {
292     struct timeval tv1;
293     struct timeval tv2;
294     char buf[1024];
295     urlnode_t node;
296     FILE *fp;
297     int loops;
298     int one;
299     int fd;
300     int us;
301     int i;
302     double total_time;
303 
304     total_time = 0.0;
305     one = 1;
306 
307     /*
308      * Make sure children's random number generators are NOT synchronized.
309      */
310     if (random_opt)
311 	srandomdev();
312 
313     for (loops = 0; loop_count == 0 || loops < loop_count; ++loops) {
314 	/*
315 	 * Random requests
316 	 */
317 	if (random_opt) {
318 	    for (i = count * 4; i; --i) {
319 		int ex1 = random() % count;
320 		int ex2 = random() % count;
321 		node = array[ex1];
322 		array[ex1] = array[ex2];
323 		array[ex2] = node;
324 	    }
325 	}
326 
327 	/*
328 	 * Run through the array
329 	 */
330 	for (i = 0; i < count; ++i) {
331 	    node = array[i];
332 
333 	    if ((fd = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
334 		perror("socket");
335 		exit(1);
336 	    }
337 	    setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));
338 	    gettimeofday(&tv1, NULL);
339 	    if (connect(fd, (void *)&node->url_sin, node->url_sin.sin_len) < 0) {
340 		gettimeofday(&tv2, NULL);
341 		us = (tv2.tv_sec - tv1.tv_sec) * 1000000;
342 		us += (int)(tv2.tv_usec - tv1.tv_usec) / 1000000;
343 		printf("connect_failure %6.2fms: http://%s:%d/%s\n",
344 		    (double)us / 1000.0, node->url_host,
345 		    ntohs(node->url_sin.sin_port),
346 		    node->url_path);
347 		close(fd);
348 		continue;
349 	    }
350 	    if ((fp = fdopen(fd, "r+")) == NULL) {
351 		perror("fdopen");
352 		exit(1);
353 	    }
354 	    fprintf(fp, "GET /%s HTTP/1.1\r\n"
355 			"Host: %s\r\n\r\n",
356 			node->url_path,
357 			node->url_host);
358 	    fflush(fp);
359 	    shutdown(fileno(fp), SHUT_WR);
360 	    while (fgets(buf, sizeof(buf), fp) != NULL)
361 		;
362 	    fclose(fp);
363 	    gettimeofday(&tv2, NULL);
364 	    us = (tv2.tv_sec - tv1.tv_sec) * 1000000;
365 	    us += (int)(tv2.tv_usec - tv1.tv_usec);
366 	    if (us > limit_opt) {
367 		printf("access_time %6.2fms: http://%s:%d/%s\n",
368 		    (double)us / 1000.0, node->url_host,
369 		    ntohs(node->url_sin.sin_port),
370 		    node->url_path);
371 	    }
372 	    total_time += (double)us / 1000000.0;
373 	}
374 	if (report_interval && (loops + 1) % report_interval == 0) {
375 		printf("loop_time: %6.3fmS avg/url %6.3fmS\n",
376 			total_time / (double)report_interval * 1000.0,
377 			total_time / (double)report_interval * 1000.0 /
378 			 (double)count);
379 		total_time = 0.0;
380 
381 		/*
382 		 * don't let the loops variable wrap if we are running
383 		 * forever, it will cause weird times to be reported.
384 		 */
385 		if (loop_count == 0)
386 			loops = 0;
387 	}
388     }
389 }
390 
391