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