xref: /freebsd/tools/tools/netrate/http/http.c (revision 190cef3d)
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
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
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
163 signal_handler(int signum __unused)
164 {
165 
166 	statep->hwd[curthread].hwd_start_signal_barrier = 1;
167 }
168 
169 static 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
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 *
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
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
235 main_sighup(int signum __unused)
236 {
237 
238 	killall();
239 }
240 
241 int
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