xref: /freebsd/tools/tools/netrate/http/http.c (revision 61e21613)
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