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
http_fetch(struct sockaddr_in * sin,char * path,int quiet)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
killall(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
signal_handler(int signum __unused)163 signal_handler(int signum __unused)
164 {
165
166 statep->hwd[curthread].hwd_start_signal_barrier = 1;
167 }
168
169 static void
signal_barrier_wait(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
signal_barrier_wakeup(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 *
http_worker(void * arg)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
usage(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
main_sighup(int signum __unused)235 main_sighup(int signum __unused)
236 {
237
238 killall();
239 }
240
241 int
main(int argc,char * argv[])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