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>