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