Merge remote-tracking branch 'origin/vendor/LIBEDIT'
[dragonfly.git] / test / stress / webstress / webstress.c
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