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