Date: Sun, 12 Nov 95 0:46:13 DNT From: kim@dde.dk (Kim Andersen) To: jkh@time.cdrom.com (Jordan K. Hubbard) Cc: hackers@FreeBSD.org Subject: Re: Anyone else think it's about time to beat a WEB server to death? Message-ID: <9511120046.AA26742@nessie.dde.dk> In-Reply-To: <8352.816054219@time.cdrom.com>; from "Jordan K. Hubbard" at Nov 10, 95 5:43 pm
next in thread | previous in thread | raw e-mail | index | archive | help
>
> > All people interested in the setup of the machines, etc, can join
> > w3test@ibenet.it by sending e-mail to me <piero@ibenet.it>
> > (no automatic daemon, sorry).
>
> I have also set up a webtest@freebsd.org mailing alias for those folks
> actually participating in setting up this test. So far, it's myself,
> Piero, Jaye and Brad. Any suggestions concerning how to run the tests
> or features that people are particularly interested in having tested
> should be sent to this alias. Thanks!
For local testing of a webservers "response" the following might be of interest:
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/fcntl.h>
#include <netinet/in.h>
#include <signal.h>
#include <netdb.h>
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <string.h>
/* Webmonkey.c --- when you want to know how your server responds to a
* hundred monkeys all pounding on it. Transactions are replayed out of
* a common-log-format log file. (We replay GETs only; sufficient data
* is not logged to properly replay PUTs. Also, HTTP authentication isn't
* done right, so sites which rely on it heavily will mainly see how fast
* their server rejects unauthorized access).
*/
/* Defaults for whose back the monkey climbs ... and how hard it works
*/
char *default_host = "www";
int default_port = 80;
int max_trans = 25; /* Maximum number of transactions at once */
int usec_per = 200000; /* Five per second for starters */
/* Mime headers sent on Monkey requests, starting with the newline which
* follows the request itself
*/
char mime_headers[] =
"\r\n"
"Accept: text/plain\r\n"
"Accept: application/x-html\r\n"
"Accept: application/html\r\n"
"Accept: text/x-html\r\n"
"Accept: text/html\r\n"
"Accept: application/x-hdf\r\n"
"Accept: application/x-netcdf\r\n"
"Accept: application/hdf\r\n"
"Accept: application/netcdf\r\n"
"Accept: audio/basic\r\n"
"Accept: audio/x-aiff\r\n"
"Accept: image/gif\r\n"
"Accept: image/jpeg\r\n"
"Accept: image/tiff\r\n"
"Accept: image/x-portable-anymap\r\n"
"Accept: image/x-portable-bitmap\r\n"
"Accept: image/x-portable-graymap\r\n"
"Accept: image/x-portable-pixmap\r\n"
"Accept: image/x-rgb\r\n"
"Accept: image/rgb\r\n"
"Accept: image/x-xbitmap\r\n"
"Accept: image/x-xpixmap\r\n"
"Accept: image/xwd\r\n"
"Accept: image/x-xwd\r\n"
"Accept: image/x-xwindowdump\r\n"
"Accept: video/mpeg\r\n"
"Accept: application/postscript\r\n"
"Accept: application/x-dvi\r\n"
"Accept: message/rfc822\r\n"
"Accept: application/x-latex\r\n"
"Accept: application/x-tex\r\n"
"Accept: application/x-texinfo\r\n"
"Accept: application/x-troff\r\n"
"Accept: application/x-troff-man\r\n"
"Accept: application/x-troff-me\r\n"
"Accept: application/x-troff-ms\r\n"
"Accept: text/richtext\r\n"
"Accept: text/tab-separated-values\r\n"
"Accept: text/x-setext\r\n"
"Accept: */*\r\n"
"User-Agent: WebMonkey 0.02\r\n"
"\r\n"
;
#define MIME_HDR_SIZE (sizeof(mime_headers) - 1)
/* Globals holding status of all current connections */
fd_set read_fds; /* FDs currently being read from */
fd_set write_fds; /* FDs currently being sent to, or
* with a connection in progress...
*/
fd_set except_fds; /* FDs currently in use */
int transactions_in_progress = 0;
int fdmax = 0;
int transactions_done = 0; /* Statistics... */
int total_msecs = 0;
/* State of sockets, for each possible socket...
*/
struct sock_status
{
char *request; /* Kept for as long as request is active */
struct timeval start_time; /* For statistics gathering */
char *data; /* Data *currently* being written */
int len; /* Amount to write */
int len_written; /* Amount of that done so far */
int state; /* State of the socket (see below) */
} sock_status[FD_SETSIZE];
/* Possible socket states:
* (note that the appropriate bits must *also* be set in the fdsets
* for anything to work...).
*/
#define CONNECT 0 /* Connecting */
#define WRITE_REQ 1 /* Writing request */
#define WRITE_HDR 2 /* Writing MIME header */
#define READ_RESPONSE 3 /* Reading response */
#define NSTATES 4
char *state_names[NSTATES] =
{
"connecting",
"writing request",
"writing MIME header",
"reading response",
};
/****************************************************************
* Time fiddling
*/
void add_usecs (struct timeval *dest, struct timeval *src, int usecs)
{
dest->tv_sec = src->tv_sec;
dest->tv_usec = src->tv_usec + usec_per;
if (dest->tv_usec > 1000000)
{
dest->tv_usec -= 1000000;
dest->tv_sec += 1;
}
}
void sub_timevals (struct timeval *dest,
struct timeval *later, struct timeval *earlier)
{
dest->tv_sec = later->tv_sec - earlier->tv_sec;
dest->tv_usec = later->tv_usec - earlier->tv_usec;
if (dest->tv_usec < 0)
{
dest->tv_usec += 1000000;
dest->tv_sec -= 1;
}
if (dest->tv_usec < 0 || dest->tv_sec < 0)
{
dest->tv_usec = dest->tv_sec = 0;
}
}
int get_msecs (struct timeval *tm)
{
return tm->tv_sec * 1000 + tm->tv_usec / 1000;
}
/****************************************************************
* Routines to manipulate sock_status structures --- set 'em up,
* tear 'em down. Note that the request is passed as malloc'ed memory
* which we "own" once we get it, so it's on us to free it afterward.
*
* These routines also take care of a lot of the globals, to have all
* that stuff in one place.
*/
void
init_sock_status (int s, int state, char *req)
{
sock_status[s].state = state;
sock_status[s].request = req;
sock_status[s].data = req;
sock_status[s].len = strlen(req);
sock_status[s].len_written = 0;
gettimeofday (&sock_status[s].start_time, NULL);
FD_SET (s, &except_fds); /* Protocol code determines when
* writes and reads are appropriate,
* but we *always* want to know about
* exceptional conditions.
*/
++transactions_in_progress;
if (s > fdmax) fdmax = s;
}
void
finalize_sock_status (int s)
{
struct timeval now, interval;
/* Common code in tearing down transactions:
* No more I/O on this descriptor.
*/
FD_CLR (s, &read_fds);
FD_CLR (s, &write_fds);
FD_CLR (s, &except_fds);
close (s);
/* No more need for request text */
free (sock_status[s].request);
/* Fiddle other globals and keep statistics */
gettimeofday (&now, 0);
sub_timevals (&interval, &now, &sock_status[s].start_time);
total_msecs += get_msecs (&interval);
transactions_done += 1;
--transactions_in_progress;
}
/****************************************************************
* Set up the globals, and find the address of the host we're
* going to flog.
*/
void
monkey_init (struct sockaddr_in *sin, char *host, int port)
{
struct hostent *hostp;
if (!(hostp = gethostbyname(host)))
{
fprintf (stderr, "Can't find host %s!\n", host);
exit (1);
}
sin->sin_family = hostp->h_addrtype;
sin->sin_port = port;
memcpy ((char*)&sin->sin_addr, hostp->h_addr, hostp->h_length);
FD_ZERO(&read_fds);
FD_ZERO(&write_fds);
FD_ZERO(&except_fds);
}
/****************************************************************
* Make a connection on a socket; set up to continue transaction
*/
void
monkeystart (char *req, struct sockaddr_in *sin)
{
int s, rv;
struct sock_status *t;
/* Get a socket, and set up to keep statistics on this connection */
if ((s = socket(sin->sin_family, SOCK_STREAM, 0)) < 0)
{
perror("webmonkey: socket");
exit(1);
}
init_sock_status (s, CONNECT, req);
/* Set up to write the request, once the connection is accepted
* (and thus, the socket shows ready for writing in select()).
*/
fcntl (s, F_SETFL, O_NDELAY);
FD_SET (s, &write_fds);
while ((rv = connect(s, (struct sockaddr *)sin, sizeof(*sin))) < 0
&& errno == EINTR)
continue;
if (rv < 0 && errno != EINPROGRESS)
{
extern void monkey_except(int);
monkey_except (s); /* Handle as other exceptional conditions */
}
}
/****************************************************************
* Continue sending an incomplete request or MIME header
* on a socket clear for writing
*/
void
monkey_write (int s)
{
struct sock_status *t = &sock_status[s];
int rv = write (s, t->data + t->len_written, t->len - t->len_written);
if (rv <= 0) return;
t->len_written += rv;
if (t->len_written == t->len)
switch (t->state)
{
case CONNECT:
t->state = WRITE_REQ;
/* and fall through... */
case WRITE_REQ:
t->state = WRITE_HDR; /* Start writing MIME headers */
t->data = mime_headers; /* (they're boilerplate) */
t->len = MIME_HDR_SIZE;
t->len_written = 0;
break;
case WRITE_HDR:
t->state = READ_RESPONSE;
FD_CLR (s, &write_fds); /* Done with (boilerplate) headers */
FD_SET (s, &read_fds); /* Read HTTP response */
break;
default:
fprintf (stderr,
"Weird, weird --- write ready on socket with state %d\n",
t->state);
FD_CLR (s, &write_fds);
break;
}
}
/* Continue reading the response to a request to which the server
* is now replying.
*/
void
monkey_read (int s)
{
char dummybuf[8192];
int rv = read (s, dummybuf, 8192);
if (rv == 0) finalize_sock_status(s);
}
/* Handle exceptions (if they occur) --- just close the damn
* socket and forget about it.
*/
void
monkey_except(int s)
{
fprintf (stderr, "Error on \"%s\", while %s\n",
sock_status[s].request, state_names[sock_status[s].state]);
finalize_sock_status(s);
}
/* Do *all* I/O for transactions currently in progress */
void
do_transactions(struct timeval *timeout)
{
fd_set my_read_fds = read_fds;
fd_set my_write_fds = write_fds;
fd_set my_except_fds = except_fds;
int nfds = select (fdmax + 1, &my_read_fds, &my_write_fds, &my_except_fds,
timeout);
int i;
for (i = 0; i <= fdmax; ++i)
{
if (FD_ISSET (i, &my_except_fds)) monkey_except(i);
else if (FD_ISSET (i, &my_read_fds)) monkey_read(i);
else if (FD_ISSET (i, &my_write_fds)) monkey_write(i);
}
}
/* Get a request out of the log file we're replaying
*/
char *
get_request (FILE *log)
{
static char reqbuffer[1024];
char *reqp;
while (fgets (reqbuffer, sizeof(reqbuffer), log))
{
char *cp = reqbuffer;
/* Find start of request... */
while (*cp && *cp != '"') ++cp;
if (!*cp) continue;
reqp = ++cp;
/* Find and flag end of request */
while (*cp && *cp != '"') ++cp;
if (!*cp) continue;
*cp = '\0';
/* Sanity check the request... */
if (!strncmp (reqp, "GET ", 4)) return strdup(reqp);
}
return NULL;
}
/* Executive */
int
main (int argc, char **argv)
{
int port = default_port;
char *host = default_host;
FILE *log = stdin;
int skip = 0;
int last_stats_printout;
int lost_transactions = 0;
struct sockaddr_in sin;
struct timeval now;
struct timeval next_start;
struct timeval interval;
while (*++argv)
{
if (!strcmp (*argv, "-host")) host = *++argv;
else if (!strcmp (*argv, "-port")) port = atoi(*++argv);
else if (!strcmp (*argv, "-max")) max_trans = atoi(*++argv);
else if (!strcmp (*argv, "-usec")) usec_per = atoi(*++argv);
else if (!strcmp (*argv, "-skip")) skip = atoi (*++argv);
else if (!strcmp (*argv, "-per")) {
double atof (char*);
double per = atof (*++argv);
usec_per = (int)(1e6 / per);
}
else if (!strcmp (*argv, "-log")) {
log = fopen (*++argv, "r");
if (log == NULL) { perror(log); exit(1); }
}
else {
fprintf (stderr,
"Usage: webmonkey [-host h] [-port p] [-max max_transactions]\n"
" [-per connects_per_second] [-log logname]\n");
exit (1);
}
}
printf ("Webmonkey: Flogging %s at port %d\n", host, port);
printf ("New transaction every %d microseconds, up to %d\n",
usec_per, max_trans);
while (skip > 0)
{
int c = getc(log);
if (c == EOF)
{
fprintf (stderr, "Skipped past end of log!\n");
exit(1);
}
if (c == '\n') --skip;
}
monkey_init (&sin, host, port);
signal (SIGPIPE, SIG_IGN);
last_stats_printout = 1;
gettimeofday (&now, 0);
add_usecs (&next_start, &now, usec_per);
while (1)
{
if ((transactions_done + lost_transactions) > last_stats_printout
&& (transactions_done + lost_transactions) % 100 == 0)
{
if (lost_transactions == 0)
printf ("Done %d transactions; average response %d msecs.\n",
transactions_done,
total_msecs / transactions_done);
else
printf ("Done %d transactions (missed %d); average response %d msecs.\n",
transactions_done, lost_transactions,
total_msecs / transactions_done);
last_stats_printout = transactions_done + lost_transactions;
}
gettimeofday (&now, 0);
if (now.tv_sec > next_start.tv_sec
|| (now.tv_sec == next_start.tv_sec
&& now.tv_usec >= next_start.tv_usec))
{
/* Time to fire off a new request */
char *request = get_request(log);
if (!request) exit(0);
add_usecs (&next_start, &next_start, usec_per);
if (transactions_in_progress > max_trans)
++lost_transactions;
else
monkeystart (request, &sin);
}
/* Do I/O, or pause, until it's time to fire off the next one. */
sub_timevals (&interval, &next_start, &now);
do_transactions (&interval);
}
}
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?9511120046.AA26742>
