1 /*- 2 * Copyright (c) 2005-2006 Robert N. M. Watson 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 * SUCH DAMAGE. 25 * 26 * $FreeBSD$ 27 */ 28 29 #include <sys/types.h> 30 #include <sys/mman.h> 31 #include <sys/socket.h> 32 #include <sys/wait.h> 33 34 #include <netinet/in.h> 35 36 #include <arpa/inet.h> 37 38 #include <err.h> 39 #include <errno.h> 40 #include <pthread.h> 41 #include <signal.h> 42 #include <stdint.h> 43 #include <stdio.h> 44 #include <stdlib.h> 45 #include <string.h> 46 #include <sysexits.h> 47 #include <unistd.h> 48 49 static int threaded; /* 1 for threaded, 0 for forked. */ 50 static int numthreads; /* Number of threads/procs. */ 51 static int numseconds; /* Length of test. */ 52 53 /* 54 * Simple, multi-threaded HTTP benchmark. Fetches a single URL using the 55 * specified parameters, and after a period of execution, reports on how it 56 * worked out. 57 */ 58 #define MAXTHREADS 128 59 #define DEFAULTTHREADS 32 60 #define DEFAULTSECONDS 20 61 #define BUFFER (48*1024) 62 #define QUIET 1 63 64 struct http_worker_description { 65 pthread_t hwd_thread; 66 pid_t hwd_pid; 67 uintmax_t hwd_count; 68 uintmax_t hwd_errorcount; 69 int hwd_start_signal_barrier; 70 }; 71 72 static struct state { 73 struct sockaddr_in sin; 74 char *path; 75 struct http_worker_description hwd[MAXTHREADS]; 76 int run_done; 77 pthread_barrier_t start_barrier; 78 } *statep; 79 80 int curthread; 81 82 /* 83 * Borrowed from sys/param.h> 84 */ 85 #define roundup(x, y) ((((x)+((y)-1))/(y))*(y)) /* to any y */ 86 87 /* 88 * Given a partially processed URL, fetch it from the specified host. 89 */ 90 static int 91 http_fetch(struct sockaddr_in *sin, char *path, int quiet) 92 { 93 u_char buffer[BUFFER]; 94 ssize_t len; 95 size_t sofar; 96 int sock; 97 98 sock = socket(PF_INET, SOCK_STREAM, 0); 99 if (sock < 0) { 100 if (!quiet) 101 warn("socket(PF_INET, SOCK_STREAM)"); 102 return (-1); 103 } 104 105 /* XXX: Mark non-blocking. */ 106 107 if (connect(sock, (struct sockaddr *)sin, sizeof(*sin)) < 0) { 108 if (!quiet) 109 warn("connect"); 110 close(sock); 111 return (-1); 112 } 113 114 /* XXX: select for connection. */ 115 116 /* Send a request. */ 117 snprintf(buffer, BUFFER, "GET %s HTTP/1.0\n\n", path); 118 sofar = 0; 119 while (sofar < strlen(buffer)) { 120 len = send(sock, buffer, strlen(buffer), 0); 121 if (len < 0) { 122 if (!quiet) 123 warn("send"); 124 close(sock); 125 return (-1); 126 } 127 if (len == 0) { 128 if (!quiet) 129 warnx("send: len == 0"); 130 } 131 sofar += len; 132 } 133 134 /* Read until done. Not very smart. */ 135 while (1) { 136 len = recv(sock, buffer, BUFFER, 0); 137 if (len < 0) { 138 if (!quiet) 139 warn("recv"); 140 close(sock); 141 return (-1); 142 } 143 if (len == 0) 144 break; 145 } 146 147 close(sock); 148 return (0); 149 } 150 151 static void 152 killall(void) 153 { 154 int i; 155 156 for (i = 0; i < numthreads; i++) { 157 if (statep->hwd[i].hwd_pid != 0) 158 kill(statep->hwd[i].hwd_pid, SIGTERM); 159 } 160 } 161 162 static void 163 signal_handler(int signum __unused) 164 { 165 166 statep->hwd[curthread].hwd_start_signal_barrier = 1; 167 } 168 169 static void 170 signal_barrier_wait(void) 171 { 172 173 /* Wait for EINTR. */ 174 if (signal(SIGHUP, signal_handler) == SIG_ERR) 175 err(-1, "signal"); 176 while (1) { 177 sleep(100); 178 if (statep->hwd[curthread].hwd_start_signal_barrier) 179 break; 180 } 181 } 182 183 static void 184 signal_barrier_wakeup(void) 185 { 186 int i; 187 188 for (i = 0; i < numthreads; i++) { 189 if (statep->hwd[i].hwd_pid != 0) 190 kill(statep->hwd[i].hwd_pid, SIGHUP); 191 } 192 } 193 194 static void * 195 http_worker(void *arg) 196 { 197 struct http_worker_description *hwdp; 198 int ret; 199 200 if (threaded) { 201 ret = pthread_barrier_wait(&statep->start_barrier); 202 if (ret != 0 && ret != PTHREAD_BARRIER_SERIAL_THREAD) 203 err(-1, "pthread_barrier_wait"); 204 } else { 205 signal_barrier_wait(); 206 } 207 208 hwdp = arg; 209 while (!statep->run_done) { 210 if (http_fetch(&statep->sin, statep->path, QUIET) < 0) { 211 hwdp->hwd_errorcount++; 212 continue; 213 } 214 /* Don't count transfers that didn't finish in time. */ 215 if (!statep->run_done) 216 hwdp->hwd_count++; 217 } 218 219 if (threaded) 220 return (NULL); 221 else 222 exit(0); 223 } 224 225 static void 226 usage(void) 227 { 228 229 fprintf(stderr, 230 "http [-n numthreads] [-s seconds] [-t] ip port path\n"); 231 exit(EX_USAGE); 232 } 233 234 static void 235 main_sighup(int signum __unused) 236 { 237 238 killall(); 239 } 240 241 int 242 main(int argc, char *argv[]) 243 { 244 int ch, error, i; 245 struct state *pagebuffer; 246 uintmax_t total; 247 size_t len; 248 pid_t pid; 249 250 numthreads = DEFAULTTHREADS; 251 numseconds = DEFAULTSECONDS; 252 while ((ch = getopt(argc, argv, "n:s:t")) != -1) { 253 switch (ch) { 254 case 'n': 255 numthreads = atoi(optarg); 256 break; 257 258 case 's': 259 numseconds = atoi(optarg); 260 break; 261 262 case 't': 263 threaded = 1; 264 break; 265 266 default: 267 usage(); 268 } 269 } 270 argc -= optind; 271 argv += optind; 272 273 if (argc != 3) 274 usage(); 275 276 if (numthreads > MAXTHREADS) 277 errx(-1, "%d exceeds max threads %d", numthreads, 278 MAXTHREADS); 279 280 len = roundup(sizeof(struct state), getpagesize()); 281 pagebuffer = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_ANON, -1, 0); 282 if (pagebuffer == MAP_FAILED) 283 err(-1, "mmap"); 284 if (minherit(pagebuffer, len, INHERIT_SHARE) < 0) 285 err(-1, "minherit"); 286 statep = pagebuffer; 287 288 bzero(&statep->sin, sizeof(statep->sin)); 289 statep->sin.sin_len = sizeof(statep->sin); 290 statep->sin.sin_family = AF_INET; 291 statep->sin.sin_addr.s_addr = inet_addr(argv[0]); 292 statep->sin.sin_port = htons(atoi(argv[1])); 293 statep->path = argv[2]; 294 295 /* 296 * Do one test retrieve so we can report the error from it, if any. 297 */ 298 if (http_fetch(&statep->sin, statep->path, 0) < 0) 299 exit(-1); 300 301 if (threaded) { 302 if (pthread_barrier_init(&statep->start_barrier, NULL, 303 numthreads) != 0) 304 err(-1, "pthread_barrier_init"); 305 } 306 307 for (i = 0; i < numthreads; i++) { 308 statep->hwd[i].hwd_count = 0; 309 if (threaded) { 310 if (pthread_create(&statep->hwd[i].hwd_thread, NULL, 311 http_worker, &statep->hwd[i]) != 0) 312 err(-1, "pthread_create"); 313 } else { 314 curthread = i; 315 pid = fork(); 316 if (pid < 0) { 317 error = errno; 318 killall(); 319 errno = error; 320 err(-1, "fork"); 321 } 322 if (pid == 0) { 323 http_worker(&statep->hwd[i]); 324 printf("Doh\n"); 325 exit(0); 326 } 327 statep->hwd[i].hwd_pid = pid; 328 } 329 } 330 if (!threaded) { 331 signal(SIGHUP, main_sighup); 332 sleep(2); 333 signal_barrier_wakeup(); 334 } 335 sleep(numseconds); 336 statep->run_done = 1; 337 if (!threaded) 338 sleep(2); 339 for (i = 0; i < numthreads; i++) { 340 if (threaded) { 341 if (pthread_join(statep->hwd[i].hwd_thread, NULL) 342 != 0) 343 err(-1, "pthread_join"); 344 } else { 345 pid = waitpid(statep->hwd[i].hwd_pid, NULL, 0); 346 if (pid == statep->hwd[i].hwd_pid) 347 statep->hwd[i].hwd_pid = 0; 348 } 349 } 350 if (!threaded) 351 killall(); 352 total = 0; 353 for (i = 0; i < numthreads; i++) 354 total += statep->hwd[i].hwd_count; 355 printf("%ju transfers/second\n", total / numseconds); 356 total = 0; 357 for (i = 0; i < numthreads; i++) 358 total += statep->hwd[i].hwd_errorcount; 359 printf("%ju errors/second\n", total / numseconds); 360 return (0); 361 } 362