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 49static int threaded; /* 1 for threaded, 0 for forked. */ 50static int numthreads; /* Number of threads/procs. */ 51static 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 64struct 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 72static 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 80int 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 */ 90static int 91http_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 151static void 152killall(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 162static void 163signal_handler(int signum) 164{ 165 166 statep->hwd[curthread].hwd_start_signal_barrier = 1; 167} 168 169static void 170signal_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 183static void 184signal_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 194static void * 195http_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 225static void 226usage(void) 227{ 228 229 fprintf(stderr, 230 "http [-n numthreads] [-s seconds] [-t] ip port path\n"); 231 exit(EX_USAGE); 232} 233 234static void 235main_sighup(int signum) 236{ 237 238 killall(); 239} 240 241int 242main(int argc, char *argv[]) 243{ 244 int ch, error, i; 245 char *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 = (struct state *)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