2 * The software known as "DragonFly" or "DragonFly BSD" is distributed under
5 * Copyright (c) 2003, 2004, 2005 The DragonFly Project. All rights reserved.
7 * This code is derived from software contributed to The DragonFly Project
8 * by Matthew Dillon <dillon@backplane.com>
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
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
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.
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
37 * $DragonFly: src/test/stress/webstress/webstress.c,v 1.1 2005/04/05 00:13:20 dillon Exp $
40 * webstress [-n num] [-r] [-f url_file] [-l limit_ms] [-c count] url url
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.
47 * If the -r option is specified the list of URLs is randomized.
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.
53 * All URLs should begin with http:// but this is optional. Only http is
56 * By default the program runs until you ^C it. The -c option may be
57 * used to limit the number of loops.
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.
66 #include <sys/types.h>
67 #include <sys/socket.h>
70 #include <netinet/in.h>
71 #include <netinet/tcp.h>
72 #include <arpa/inet.h>
82 typedef struct urlnode {
83 struct urlnode *url_next;
84 struct sockaddr_in url_sin;
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);
96 int limit_opt = 1000000;
97 int report_interval = 100;
100 urlnode_t *url_nextp = &url_base;
104 main(int ac, char **av)
112 while ((ch = getopt(ac, av, "c:f:l:n:r")) != -1) {
113 printf("CH %c\n", ch);
116 loop_count = strtol(optarg, NULL, 0);
117 if (report_interval > loop_count)
118 report_interval = loop_count;
121 fork_opt = strtol(optarg, NULL, 0);
127 read_url_file(optarg);
130 limit_opt = strtol(optarg, NULL, 0) * 1000;
140 for (i = 0; i < ac; ++i)
142 if (url_base == NULL)
146 * Convert the list to an array
148 array = malloc(sizeof(urlnode_t) * url_count);
149 for (i = 0, node = url_base; i < url_count; ++i, node = node->url_next) {
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),
164 printf("Running...\n");
167 * Fork children and start the test
169 for (i = 0; i < fork_opt; ++i) {
170 if ((pid = fork()) == 0) {
171 run_test(array, url_count);
173 } else if (pid == (pid_t)-1) {
174 printf("unable to fork child %d\n", i);
178 while (wait(NULL) >= 0 || errno == EINTR)
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"
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",
207 read_url_file(const char *path)
213 if ((fi = fopen(path, "r")) != NULL) {
214 while (fgets(buf, sizeof(buf), fi) != NULL) {
218 if (len && buf[len-1] == '\n')
224 fprintf(stderr, "Unable to open %s\n", path);
231 add_url(const char *url)
240 node = malloc(sizeof(*node));
241 bzero(node, sizeof(*node));
244 if (strncmp(url, "http://", 7) == 0)
246 if ((ptr = strchr(base, '/')) == NULL) {
247 fprintf(stderr, "malformed URL: %s\n", base);
251 hostname = malloc(ptr - base + 1);
252 bcopy(base, hostname, ptr - base);
253 hostname[ptr - base] = 0;
255 if ((ptr = strrchr(hostname, ':')) != NULL) {
256 *strrchr(hostname, ':') = 0;
258 node->url_sin.sin_port = htons(strtol(ptr, NULL, 0));
260 node->url_sin.sin_port = htons(80);
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);
266 fprintf(stderr, "unable to parse host/ip: %s (%s)\n",
267 hostname, strerror(errno));
272 if ((hen = gethostbyname(hostname)) == NULL) {
273 fprintf(stderr, "unable to resolve host: %s (%s)\n",
274 hostname, hstrerror(h_errno));
278 bcopy(hen->h_addr, &node->url_sin.sin_addr, hen->h_length);
279 node->url_sin.sin_family = hen->h_addrtype;
281 node->url_host = strdup(hostname);
282 node->url_path = strdup(base);
284 url_nextp = &node->url_next;
290 run_test(urlnode_t *array, int count)
308 * Make sure children's random number generators are NOT synchronized.
313 for (loops = 0; loop_count == 0 || loops < loop_count; ++loops) {
318 for (i = count * 4; i; --i) {
319 int ex1 = random() % count;
320 int ex2 = random() % count;
322 array[ex1] = array[ex2];
328 * Run through the array
330 for (i = 0; i < count; ++i) {
333 if ((fd = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
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),
350 if ((fp = fdopen(fd, "r+")) == NULL) {
354 fprintf(fp, "GET /%s HTTP/1.1\r\n"
359 shutdown(fileno(fp), SHUT_WR);
360 while (fgets(buf, sizeof(buf), fp) != NULL)
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),
372 total_time += (double)us / 1000000.0;
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 /
382 * don't let the loops variable wrap if we are running
383 * forever, it will cause weird times to be reported.