1150990Srwatson/*-
2155892Srwatson * Copyright (c) 2005-2006 Robert N. M. Watson
3150990Srwatson * All rights reserved.
4150990Srwatson *
5150990Srwatson * Redistribution and use in source and binary forms, with or without
6150990Srwatson * modification, are permitted provided that the following conditions
7150990Srwatson * are met:
8150990Srwatson * 1. Redistributions of source code must retain the above copyright
9150990Srwatson *    notice, this list of conditions and the following disclaimer.
10150990Srwatson * 2. Redistributions in binary form must reproduce the above copyright
11150990Srwatson *    notice, this list of conditions and the following disclaimer in the
12150990Srwatson *    documentation and/or other materials provided with the distribution.
13150990Srwatson *
14150990Srwatson * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15150990Srwatson * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16150990Srwatson * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17150990Srwatson * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18150990Srwatson * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19150990Srwatson * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20150990Srwatson * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21150990Srwatson * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22150990Srwatson * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23150990Srwatson * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24150990Srwatson * SUCH DAMAGE.
25150990Srwatson *
26150990Srwatson * $FreeBSD$
27150990Srwatson */
28150990Srwatson
29150990Srwatson#include <sys/types.h>
30155892Srwatson#include <sys/mman.h>
31150990Srwatson#include <sys/socket.h>
32155892Srwatson#include <sys/wait.h>
33150990Srwatson
34150990Srwatson#include <netinet/in.h>
35150990Srwatson
36150990Srwatson#include <arpa/inet.h>
37150990Srwatson
38150990Srwatson#include <err.h>
39155892Srwatson#include <errno.h>
40150990Srwatson#include <pthread.h>
41155892Srwatson#include <signal.h>
42151705Spjd#include <stdint.h>
43150990Srwatson#include <stdio.h>
44150990Srwatson#include <stdlib.h>
45150990Srwatson#include <string.h>
46155892Srwatson#include <sysexits.h>
47150990Srwatson#include <unistd.h>
48150990Srwatson
49155892Srwatsonstatic int	threaded;		/* 1 for threaded, 0 for forked. */
50155892Srwatsonstatic int	numthreads;		/* Number of threads/procs. */
51155892Srwatsonstatic int	numseconds;		/* Length of test. */
52155892Srwatson
53150990Srwatson/*
54150990Srwatson * Simple, multi-threaded HTTP benchmark.  Fetches a single URL using the
55150990Srwatson * specified parameters, and after a period of execution, reports on how it
56150990Srwatson * worked out.
57150990Srwatson */
58155892Srwatson#define	MAXTHREADS	128
59155892Srwatson#define	DEFAULTTHREADS	32
60155892Srwatson#define	DEFAULTSECONDS	20
61150990Srwatson#define	BUFFER	(48*1024)
62150990Srwatson#define	QUIET	1
63150990Srwatson
64150990Srwatsonstruct http_worker_description {
65150990Srwatson	pthread_t	hwd_thread;
66155892Srwatson	pid_t		hwd_pid;
67151705Spjd	uintmax_t	hwd_count;
68151705Spjd	uintmax_t	hwd_errorcount;
69155892Srwatson	int		hwd_start_signal_barrier;
70150990Srwatson};
71150990Srwatson
72155892Srwatsonstatic struct state {
73155892Srwatson	struct sockaddr_in		 sin;
74155892Srwatson	char				*path;
75155892Srwatson	struct http_worker_description	 hwd[MAXTHREADS];
76155892Srwatson	int				 run_done;
77155892Srwatson	pthread_barrier_t		 start_barrier;
78155892Srwatson} *statep;
79150990Srwatson
80155892Srwatsonint curthread;
81155892Srwatson
82150990Srwatson/*
83155892Srwatson * Borrowed from sys/param.h>
84155892Srwatson */
85155892Srwatson#define	roundup(x, y)	((((x)+((y)-1))/(y))*(y))	/* to any y */
86155892Srwatson
87155892Srwatson/*
88150990Srwatson * Given a partially processed URL, fetch it from the specified host.
89150990Srwatson */
90150990Srwatsonstatic int
91150990Srwatsonhttp_fetch(struct sockaddr_in *sin, char *path, int quiet)
92150990Srwatson{
93150990Srwatson	u_char buffer[BUFFER];
94150990Srwatson	ssize_t len;
95150990Srwatson	size_t sofar;
96150990Srwatson	int sock;
97150990Srwatson
98150990Srwatson	sock = socket(PF_INET, SOCK_STREAM, 0);
99150990Srwatson	if (sock < 0) {
100150990Srwatson		if (!quiet)
101150990Srwatson			warn("socket(PF_INET, SOCK_STREAM)");
102150990Srwatson		return (-1);
103150990Srwatson	}
104150990Srwatson
105155892Srwatson	/* XXX: Mark non-blocking. */
106155892Srwatson
107150990Srwatson	if (connect(sock, (struct sockaddr *)sin, sizeof(*sin)) < 0) {
108150990Srwatson		if (!quiet)
109150990Srwatson			warn("connect");
110150990Srwatson		close(sock);
111150990Srwatson		return (-1);
112150990Srwatson	}
113150990Srwatson
114155892Srwatson	/* XXX: select for connection. */
115155892Srwatson
116150990Srwatson	/* Send a request. */
117150990Srwatson	snprintf(buffer, BUFFER, "GET %s HTTP/1.0\n\n", path);
118150990Srwatson	sofar = 0;
119150990Srwatson	while (sofar < strlen(buffer)) {
120150990Srwatson		len = send(sock, buffer, strlen(buffer), 0);
121150990Srwatson		if (len < 0) {
122150990Srwatson			if (!quiet)
123150990Srwatson				warn("send");
124150990Srwatson			close(sock);
125150990Srwatson			return (-1);
126150990Srwatson		}
127150990Srwatson		if (len == 0) {
128150990Srwatson			if (!quiet)
129150990Srwatson				warnx("send: len == 0");
130150990Srwatson		}
131150990Srwatson		sofar += len;
132150990Srwatson	}
133150990Srwatson
134150990Srwatson	/* Read until done.  Not very smart. */
135150990Srwatson	while (1) {
136150990Srwatson		len = recv(sock, buffer, BUFFER, 0);
137150990Srwatson		if (len < 0) {
138150990Srwatson			if (!quiet)
139150990Srwatson				warn("recv");
140150990Srwatson			close(sock);
141150990Srwatson			return (-1);
142150990Srwatson		}
143150990Srwatson		if (len == 0)
144150990Srwatson			break;
145150990Srwatson	}
146150990Srwatson
147150990Srwatson	close(sock);
148150990Srwatson	return (0);
149150990Srwatson}
150150990Srwatson
151155892Srwatsonstatic void
152155892Srwatsonkillall(void)
153155892Srwatson{
154155892Srwatson	int i;
155155892Srwatson
156155892Srwatson	for (i = 0; i < numthreads; i++) {
157155892Srwatson		if (statep->hwd[i].hwd_pid != 0)
158155892Srwatson			kill(statep->hwd[i].hwd_pid, SIGTERM);
159155892Srwatson	}
160155892Srwatson}
161155892Srwatson
162155892Srwatsonstatic void
163155892Srwatsonsignal_handler(int signum)
164155892Srwatson{
165155892Srwatson
166155892Srwatson	statep->hwd[curthread].hwd_start_signal_barrier = 1;
167155892Srwatson}
168155892Srwatson
169155892Srwatsonstatic void
170155892Srwatsonsignal_barrier_wait(void)
171155892Srwatson{
172155892Srwatson
173155892Srwatson	/* Wait for EINTR. */
174155892Srwatson	if (signal(SIGHUP, signal_handler) == SIG_ERR)
175155892Srwatson		err(-1, "signal");
176155892Srwatson	while (1) {
177155892Srwatson		sleep(100);
178155892Srwatson		if (statep->hwd[curthread].hwd_start_signal_barrier)
179155892Srwatson			break;
180155892Srwatson	}
181155892Srwatson}
182155892Srwatson
183155892Srwatsonstatic void
184155892Srwatsonsignal_barrier_wakeup(void)
185155892Srwatson{
186155892Srwatson	int i;
187155892Srwatson
188155892Srwatson	for (i = 0; i < numthreads; i++) {
189155892Srwatson		if (statep->hwd[i].hwd_pid != 0)
190155892Srwatson			kill(statep->hwd[i].hwd_pid, SIGHUP);
191155892Srwatson	}
192155892Srwatson}
193155892Srwatson
194150990Srwatsonstatic void *
195150990Srwatsonhttp_worker(void *arg)
196150990Srwatson{
197150990Srwatson	struct http_worker_description *hwdp;
198150990Srwatson	int ret;
199150990Srwatson
200155892Srwatson	if (threaded) {
201155892Srwatson		ret = pthread_barrier_wait(&statep->start_barrier);
202155892Srwatson		if (ret != 0 && ret != PTHREAD_BARRIER_SERIAL_THREAD)
203155892Srwatson			err(-1, "pthread_barrier_wait");
204155892Srwatson	} else {
205155892Srwatson		signal_barrier_wait();
206155892Srwatson	}
207150990Srwatson
208150990Srwatson	hwdp = arg;
209155892Srwatson	while (!statep->run_done) {
210155892Srwatson		if (http_fetch(&statep->sin, statep->path, QUIET) < 0) {
211150990Srwatson			hwdp->hwd_errorcount++;
212150990Srwatson			continue;
213150990Srwatson		}
214150990Srwatson		/* Don't count transfers that didn't finish in time. */
215155892Srwatson		if (!statep->run_done)
216150990Srwatson			hwdp->hwd_count++;
217150990Srwatson	}
218150990Srwatson
219155892Srwatson	if (threaded)
220155892Srwatson		return (NULL);
221155892Srwatson	else
222155892Srwatson		exit(0);
223150990Srwatson}
224150990Srwatson
225155892Srwatsonstatic void
226155892Srwatsonusage(void)
227155892Srwatson{
228155892Srwatson
229155892Srwatson	fprintf(stderr,
230155892Srwatson	    "http [-n numthreads] [-s seconds] [-t] ip port path\n");
231155892Srwatson	exit(EX_USAGE);
232155892Srwatson}
233155892Srwatson
234155892Srwatsonstatic void
235155892Srwatsonmain_sighup(int signum)
236155892Srwatson{
237155892Srwatson
238155892Srwatson	killall();
239155892Srwatson}
240155892Srwatson
241150990Srwatsonint
242150990Srwatsonmain(int argc, char *argv[])
243150990Srwatson{
244155892Srwatson	int ch, error, i;
245155892Srwatson	char *pagebuffer;
246151705Spjd	uintmax_t total;
247155892Srwatson	size_t len;
248155892Srwatson	pid_t pid;
249150990Srwatson
250155892Srwatson	numthreads = DEFAULTTHREADS;
251155892Srwatson	numseconds = DEFAULTSECONDS;
252155892Srwatson	while ((ch = getopt(argc, argv, "n:s:t")) != -1) {
253155892Srwatson		switch (ch) {
254155892Srwatson		case 'n':
255155892Srwatson			numthreads = atoi(optarg);
256155892Srwatson			break;
257150990Srwatson
258155892Srwatson		case 's':
259155892Srwatson			numseconds = atoi(optarg);
260155892Srwatson			break;
261150990Srwatson
262155892Srwatson		case 't':
263155892Srwatson			threaded = 1;
264155892Srwatson			break;
265155892Srwatson
266155892Srwatson		default:
267155892Srwatson			usage();
268155892Srwatson		}
269155892Srwatson	}
270155892Srwatson	argc -= optind;
271155892Srwatson	argv += optind;
272155892Srwatson
273155892Srwatson	if (argc != 3)
274155892Srwatson		usage();
275155892Srwatson
276161861Srwatson	if (numthreads > MAXTHREADS)
277161861Srwatson		errx(-1, "%d exceeds max threads %d", numthreads,
278161861Srwatson		    MAXTHREADS);
279161861Srwatson
280155892Srwatson	len = roundup(sizeof(struct state), getpagesize());
281155892Srwatson	pagebuffer = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_ANON, -1, 0);
282155892Srwatson	if (pagebuffer == MAP_FAILED)
283155892Srwatson		err(-1, "mmap");
284155892Srwatson	if (minherit(pagebuffer, len, INHERIT_SHARE) < 0)
285155892Srwatson		err(-1, "minherit");
286155892Srwatson	statep = (struct state *)pagebuffer;
287155892Srwatson
288155892Srwatson	bzero(&statep->sin, sizeof(statep->sin));
289155892Srwatson	statep->sin.sin_len = sizeof(statep->sin);
290155892Srwatson	statep->sin.sin_family = AF_INET;
291155892Srwatson	statep->sin.sin_addr.s_addr = inet_addr(argv[0]);
292155892Srwatson	statep->sin.sin_port = htons(atoi(argv[1]));
293155892Srwatson	statep->path = argv[2];
294155892Srwatson
295150990Srwatson	/*
296150990Srwatson	 * Do one test retrieve so we can report the error from it, if any.
297150990Srwatson	 */
298155892Srwatson	if (http_fetch(&statep->sin, statep->path, 0) < 0)
299150990Srwatson		exit(-1);
300150990Srwatson
301155892Srwatson	if (threaded) {
302155892Srwatson		if (pthread_barrier_init(&statep->start_barrier, NULL,
303203800Sru		    numthreads) != 0)
304203800Sru			err(-1, "pthread_barrier_init");
305155892Srwatson	}
306150990Srwatson
307155892Srwatson	for (i = 0; i < numthreads; i++) {
308155892Srwatson		statep->hwd[i].hwd_count = 0;
309155892Srwatson		if (threaded) {
310155892Srwatson			if (pthread_create(&statep->hwd[i].hwd_thread, NULL,
311203800Sru			    http_worker, &statep->hwd[i]) != 0)
312155892Srwatson				err(-1, "pthread_create");
313155892Srwatson		} else {
314155892Srwatson			curthread = i;
315155892Srwatson			pid = fork();
316155892Srwatson			if (pid < 0) {
317155892Srwatson				error = errno;
318155892Srwatson				killall();
319155892Srwatson				errno = error;
320155892Srwatson				err(-1, "fork");
321155892Srwatson			}
322155892Srwatson			if (pid == 0) {
323155892Srwatson				http_worker(&statep->hwd[i]);
324155892Srwatson				printf("Doh\n");
325155892Srwatson				exit(0);
326155892Srwatson			}
327155892Srwatson			statep->hwd[i].hwd_pid = pid;
328155892Srwatson		}
329150990Srwatson	}
330155892Srwatson	if (!threaded) {
331155892Srwatson		signal(SIGHUP, main_sighup);
332155892Srwatson		sleep(2);
333155892Srwatson		signal_barrier_wakeup();
334150990Srwatson	}
335155892Srwatson	sleep(numseconds);
336155892Srwatson	statep->run_done = 1;
337155892Srwatson	if (!threaded)
338155892Srwatson		sleep(2);
339155892Srwatson	for (i = 0; i < numthreads; i++) {
340155892Srwatson		if (threaded) {
341155892Srwatson			if (pthread_join(statep->hwd[i].hwd_thread, NULL)
342203800Sru			    != 0)
343155892Srwatson				err(-1, "pthread_join");
344155892Srwatson		} else {
345155892Srwatson			pid = waitpid(statep->hwd[i].hwd_pid, NULL, 0);
346155892Srwatson			if (pid == statep->hwd[i].hwd_pid)
347155892Srwatson				statep->hwd[i].hwd_pid = 0;
348155892Srwatson		}
349155892Srwatson	}
350155892Srwatson	if (!threaded)
351155892Srwatson		killall();
352150990Srwatson	total = 0;
353155892Srwatson	for (i = 0; i < numthreads; i++)
354155892Srwatson		total += statep->hwd[i].hwd_count;
355155892Srwatson	printf("%ju transfers/second\n", total / numseconds);
356150990Srwatson	total = 0;
357155892Srwatson	for (i = 0; i < numthreads; i++)
358155892Srwatson		total += statep->hwd[i].hwd_errorcount;
359155892Srwatson	printf("%ju errors/second\n", total / numseconds);
360150990Srwatson	return (0);
361150990Srwatson}
362