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