Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Import mar2006 tarball from http://acme.com/software/http_load/

  • Loading branch information...
commit ae288cfb14089dead68701620881c05735009c33 0 parents
@ConradIrwin authored
10 FILES
@@ -0,0 +1,10 @@
+Makefile
+README
+http_load.1
+http_load.c
+make_test_files
+port.h
+timers.c
+timers.h
+version.h
+FILES
54 Makefile
@@ -0,0 +1,54 @@
+# Makefile for http_load
+
+# CONFIGURE: If you are using a SystemV-based operating system, such as
+# Solaris, you will need to uncomment this definition.
+#SYSV_LIBS = -lnsl -lsocket -lresolv
+
+# CONFIGURE: If you want to compile in support for https, uncomment these
+# definitions. You will need to have already built OpenSSL, available at
+# http://www.openssl.org/ Make sure the SSL_TREE definition points to the
+# tree with your OpenSSL installation - depending on how you installed it,
+# it may be in /usr/local instead of /usr/local/ssl.
+#SSL_TREE = /usr/local/ssl
+#SSL_DEFS = -DUSE_SSL
+#SSL_INC = -I$(SSL_TREE)/include
+#SSL_LIBS = -L$(SSL_TREE)/lib -lssl -lcrypto
+
+
+BINDIR = /usr/local/bin
+MANDIR = /usr/local/man/man1
+CC = gcc -Wall
+CFLAGS = -O $(SRANDOM_DEFS) $(SSL_DEFS) $(SSL_INC)
+#CFLAGS = -g $(SRANDOM_DEFS) $(SSL_DEFS) $(SSL_INC)
+LDFLAGS = -s $(SSL_LIBS) $(SYSV_LIBS)
+#LDFLAGS = -g $(SSL_LIBS) $(SYSV_LIBS)
+
+all: http_load
+
+http_load: http_load.o timers.o
+ $(CC) $(CFLAGS) http_load.o timers.o $(LDFLAGS) -o http_load
+
+http_load.o: http_load.c timers.h port.h
+ $(CC) $(CFLAGS) -c http_load.c
+
+timers.o: timers.c timers.h
+ $(CC) $(CFLAGS) -c timers.c
+
+install: all
+ rm -f $(BINDIR)/http_load
+ cp http_load $(BINDIR)
+ rm -f $(MANDIR)/http_load.1
+ cp http_load.1 $(MANDIR)
+
+clean:
+ rm -f http_load *.o core core.* *.core
+
+tar:
+ @name=`sed -n -e '/define VERSION /!d' -e 's,.*http_load ,http_load-,' -e 's,",,p' version.h` ; \
+ rm -rf $$name ; \
+ mkdir $$name ; \
+ tar cf - `cat FILES` | ( cd $$name ; tar xfBp - ) ; \
+ chmod 644 $$name/Makefile ; \
+ tar cf $$name.tar $$name ; \
+ rm -rf $$name ; \
+ gzip $$name.tar
29 README
@@ -0,0 +1,29 @@
+ http_load - multiprocessing http test client
+ version of 12mar2006
+
+http_load runs multiple http fetches in parallel, to test the
+throughput of a web server. However unlike most such test clients,
+it runs in a single process, so it doesn't bog down the client
+machine. It can be configured to do https fetches as well.
+
+See the manual entry for more details.
+
+Files in this distribution:
+
+ README this
+ Makefile guess
+ http_load.c source file
+ http_load.1 manual entry
+ timers.c timers package
+ timers.h headers for timers package
+ make_test_files simple script to create a set of test files
+
+To build: If you're on a SysV-like machine (which includes old Linux systems
+but not new Linux systems), edit the Makefile and uncomment the SYSV_LIBS
+line. If you're doing SSL, uncomment those lines too. Otherwise, just do
+a make.
+
+Feedback is welcome - send bug reports, enhancements, checks, money
+orders, etc. to the addresses below.
+
+ Jef Poskanzer jef@mail.acme.com http://www.acme.com/jef/
159 http_load.1
@@ -0,0 +1,159 @@
+.TH http_load 1 "15 November 2001"
+.SH NAME
+http_load - multiprocessing http test client
+.SH SYNOPSIS
+.B http_load
+.RB [ -checksum ]
+.RB [ -throttle ]
+.RB [ -proxy
+.IR host:port ]
+.RB [ -verbose ]
+.RB [ -timeout
+.IR secs ]
+.RB [ -sip
+.IR sip_file ]
+.RB [ -cipher
+.IR str ]
+.RI (
+.BI -parallel
+.IR N
+.RI |
+.BI -rate
+.IR N
+.RB [ -jitter ]
+.RI )
+.RI (
+.BI -fetches
+.IR N
+.RI |
+.BI -seconds
+.IR N
+.RI )
+.I url_file
+.SH DESCRIPTION
+.PP
+.I http_load
+runs multiple http fetches in parallel, to test the throughput
+of a web server.
+However unlike most such test clients, it runs in a single process,
+so it doesn't bog down the client machine.
+It can be configured to do https fetches as well.
+.PP
+The -checksum flag tells
+.I http_load
+to do checksums on the files fetched, to make sure they came across ok.
+The checksums are computed the first time each URL gets fetched,
+and then recomputed and compared on each subsequent fetch.
+Without the -checksum flag only the byte count is checked.
+.PP
+The -throttle flag tells
+.I http_load
+to throttle its consumption of data to 33.6Kbps, to simulate access
+by modem users.
+.PP
+The -proxy flag lets you run http_load through a web proxy.
+.PP
+The -verbose flag tells
+.I http_load
+to put out progress reports every minute on stderr.
+.PP
+The -timeout flag specifies how long to wait on idle connections before
+giving up.
+The default is 60 seconds.
+.PP
+The -sip flag lets you specify a file containing numeric IP addresses
+(not hostnames), one per line.
+These get used randomly as the *source* address of connections.
+They must be real routable addresses on your machine, created
+with ifconfig, in order for this to work.
+The advantage of using this option is you can make one client machine
+look like a whole bank of machines, as far as the server knows.
+.PP
+The -cipher flag is only available if you have SSL support compiled in.
+It specifies a cipher set to use.
+By default, http_load will negotiate the highest security that the server
+has available, which is often higher (and slower) than typical browsers
+will negotiate.
+An example of a cipher set might be "RC4-MD5" - this will run
+considerably faster than the default.
+In addition to specifying a raw cipher string, there are three built-in
+cipher sets accessible by keywords:
+.nf
+ * fastsec - fast security - RC4-MD5
+ * highsec - high security - DES-CBC3-SHA
+ * paranoid - ultra high security - AES256-SHA
+.fi
+Of course, not all servers are guaranteed to implement these combinations.
+.PP
+One start specifier, either -parallel or -rate, is required.
+-parallel tells
+.I http_load
+to keep that many parallel fetches going simultaneously.
+-rate tells
+.I http_load
+to start that many new connections each second.
+If you use the -rate start specifier, you can also give the -jitter
+flag, telling
+.I http_load
+to vary the rate randomly by about 10%.
+.PP
+One end specifier, either -fetches or -seconds, is required.
+-fetches tells
+.I http_load
+to quit when that many fetches have been completed.
+-seconds tells
+.I http_load
+to quit after that many seconds have elapsed.
+.PP
+The url_file is just a list of URLs, one per line.
+The URLs that get fetched are chosen randomly from this file.
+.PP
+All flags may be abbreviated to a single letter.
+.PP
+Note that while the end specifier is obeyed precisely, the start specifier
+is only approximate.
+If you use the -rate flag,
+.I http_load
+will make its best effort to start connections at that rate, but may
+not succeed.
+And if you use the -parallel flag,
+.I http_load
+will attempt to keep that many simultaneous connections going, but
+may fail to keep up if the server is very fast.
+.PP
+Sample run:
+.nf
+ % http_load -rate 2 -seconds 300 urls
+ 591 fetches, 8 max parallel, 5.33606e+06 bytes, in 300 seconds
+ 9028.87 mean bytes/connection
+ 1.97 fetches/sec, 17786.9 bytes/sec
+ msecs/connect: 28.8932 mean, 44.243 max, 24.488 min
+ msecs/first-response: 63.5362 mean, 81.624 max, 57.803 min
+ HTTP response codes:
+ code 200 -- 591
+.fi
+.SH "SEE ALSO"
+http_ping(1)
+.SH AUTHOR
+Copyright © 1998,1999,2001 by Jef Poskanzer <jef@mail.acme.com>.
+All rights reserved.
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
1,857 http_load.c
@@ -0,0 +1,1857 @@
+/* http_load - multiprocessing http test client
+**
+** Copyright © 1998,1999,2001 by Jef Poskanzer <jef@mail.acme.com>.
+** All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+** 1. Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** 2. Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+** SUCH DAMAGE.
+*/
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <errno.h>
+#include <signal.h>
+
+#ifdef USE_SSL
+#include <openssl/ssl.h>
+#include <openssl/rand.h>
+#include <openssl/err.h>
+#endif
+
+#include "version.h"
+#include "port.h"
+#include "timers.h"
+
+#if defined(AF_INET6) && defined(IN6_IS_ADDR_V4MAPPED)
+#define USE_IPV6
+#endif
+
+#define max(a,b) ((a)>=(b)?(a):(b))
+#define min(a,b) ((a)<=(b)?(a):(b))
+
+/* How long a connection can stay idle before we give up on it. */
+#define IDLE_SECS 60
+
+/* Default max bytes/second in throttle mode. */
+#define THROTTLE 3360
+
+/* How often to show progress reports. */
+#define PROGRESS_SECS 60
+
+/* How many file descriptors to not use. */
+#define RESERVED_FDS 3
+
+
+typedef struct {
+ char* url_str;
+ int protocol;
+ char* hostname;
+ unsigned short port;
+#ifdef USE_IPV6
+ struct sockaddr_in6 sa;
+#else /* USE_IPV6 */
+ struct sockaddr_in sa;
+#endif /* USE_IPV6 */
+ int sa_len, sock_family, sock_type, sock_protocol;
+ char* filename;
+ int got_bytes;
+ long bytes;
+ int got_checksum;
+ long checksum;
+ } url;
+static url* urls;
+static int num_urls, max_urls;
+
+typedef struct {
+ char* str;
+ struct sockaddr_in sa;
+ } sip;
+static sip* sips;
+static int num_sips, max_sips;
+
+/* Protocol symbols. */
+#define PROTO_HTTP 0
+#ifdef USE_SSL
+#define PROTO_HTTPS 1
+#endif
+
+typedef struct {
+ int url_num;
+ struct sockaddr_in sa;
+ int sa_len;
+ int conn_fd;
+#ifdef USE_SSL
+ SSL* ssl;
+#endif
+ int conn_state, header_state;
+ int did_connect, did_response;
+ struct timeval started_at;
+ struct timeval connect_at;
+ struct timeval request_at;
+ struct timeval response_at;
+ Timer* idle_timer;
+ Timer* wakeup_timer;
+ long content_length;
+ long bytes;
+ long checksum;
+ int http_status;
+ } connection;
+static connection* connections;
+static int max_connections, num_connections, max_parallel;
+
+static int http_status_counts[1000]; /* room for all three-digit statuses */
+
+#define CNST_FREE 0
+#define CNST_CONNECTING 1
+#define CNST_HEADERS 2
+#define CNST_READING 3
+#define CNST_PAUSING 4
+
+#define HDST_LINE1_PROTOCOL 0
+#define HDST_LINE1_WHITESPACE 1
+#define HDST_LINE1_STATUS 2
+#define HDST_BOL 10
+#define HDST_TEXT 11
+#define HDST_LF 12
+#define HDST_CR 13
+#define HDST_CRLF 14
+#define HDST_CRLFCR 15
+#define HDST_C 20
+#define HDST_CO 21
+#define HDST_CON 22
+#define HDST_CONT 23
+#define HDST_CONTE 24
+#define HDST_CONTEN 25
+#define HDST_CONTENT 26
+#define HDST_CONTENT_ 27
+#define HDST_CONTENT_L 28
+#define HDST_CONTENT_LE 29
+#define HDST_CONTENT_LEN 30
+#define HDST_CONTENT_LENG 31
+#define HDST_CONTENT_LENGT 32
+#define HDST_CONTENT_LENGTH 33
+#define HDST_CONTENT_LENGTH_COLON 34
+#define HDST_CONTENT_LENGTH_COLON_WHITESPACE 35
+#define HDST_CONTENT_LENGTH_COLON_WHITESPACE_NUM 36
+
+static char* argv0;
+static int do_checksum, do_throttle, do_verbose, do_jitter, do_proxy;
+static float throttle;
+static int idle_secs;
+static char* proxy_hostname;
+static unsigned short proxy_port;
+
+static struct timeval start_at;
+static int fetches_started, connects_completed, responses_completed, fetches_completed;
+static long long total_bytes;
+static long long total_connect_usecs, max_connect_usecs, min_connect_usecs;
+static long long total_response_usecs, max_response_usecs, min_response_usecs;
+int total_timeouts, total_badbytes, total_badchecksums;
+
+static long start_interval, low_interval, high_interval, range_interval;
+
+#ifdef USE_SSL
+static SSL_CTX* ssl_ctx = (SSL_CTX*) 0;
+static char* cipher = (char*) 0;
+#endif
+
+/* Forwards. */
+static void usage( void );
+static void read_url_file( char* url_file );
+static void lookup_address( int url_num );
+static void read_sip_file( char* sip_file );
+static void start_connection( struct timeval* nowP );
+static void start_socket( int url_num, int cnum, struct timeval* nowP );
+static void handle_connect( int cnum, struct timeval* nowP, int double_check );
+static void handle_read( int cnum, struct timeval* nowP );
+static void idle_connection( ClientData client_data, struct timeval* nowP );
+static void wakeup_connection( ClientData client_data, struct timeval* nowP );
+static void close_connection( int cnum );
+static void progress_report( ClientData client_data, struct timeval* nowP );
+static void start_timer( ClientData client_data, struct timeval* nowP );
+static void end_timer( ClientData client_data, struct timeval* nowP );
+static void finish( struct timeval* nowP );
+static long long delta_timeval( struct timeval* start, struct timeval* finish );
+static void* malloc_check( size_t size );
+static void* realloc_check( void* ptr, size_t size );
+static char* strdup_check( char* str );
+static void check( void* ptr );
+
+
+int
+main( int argc, char** argv )
+ {
+ int argn;
+ int start;
+#define START_NONE 0
+#define START_PARALLEL 1
+#define START_RATE 2
+ int start_parallel = -1, start_rate = -1;
+ int end;
+#define END_NONE 0
+#define END_FETCHES 1
+#define END_SECONDS 2
+ int end_fetches = -1, end_seconds = -1;
+ int cnum;
+ char* url_file;
+ char* sip_file;
+#ifdef RLIMIT_NOFILE
+ struct rlimit limits;
+#endif /* RLIMIT_NOFILE */
+ fd_set rfdset;
+ fd_set wfdset;
+ struct timeval now;
+ int i, r;
+
+ max_connections = 64 - RESERVED_FDS; /* a guess */
+#ifdef RLIMIT_NOFILE
+ /* Try and increase the limit on # of files to the maximum. */
+ if ( getrlimit( RLIMIT_NOFILE, &limits ) == 0 )
+ {
+ if ( limits.rlim_cur != limits.rlim_max )
+ {
+ if ( limits.rlim_max == RLIM_INFINITY )
+ limits.rlim_cur = 8192; /* arbitrary */
+ else if ( limits.rlim_max > limits.rlim_cur )
+ limits.rlim_cur = limits.rlim_max;
+ (void) setrlimit( RLIMIT_NOFILE, &limits );
+ }
+ max_connections = limits.rlim_cur - RESERVED_FDS;
+ }
+#endif /* RLIMIT_NOFILE */
+
+ /* Parse args. */
+ argv0 = argv[0];
+ argn = 1;
+ do_checksum = do_throttle = do_verbose = do_jitter = do_proxy = 0;
+ throttle = THROTTLE;
+ sip_file = (char*) 0;
+ idle_secs = IDLE_SECS;
+ start = START_NONE;
+ end = END_NONE;
+ while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
+ {
+ if ( strncmp( argv[argn], "-checksum", strlen( argv[argn] ) ) == 0 )
+ do_checksum = 1;
+ else if ( strncmp( argv[argn], "-throttle", strlen( argv[argn] ) ) == 0 )
+ do_throttle = 1;
+ else if ( strncmp( argv[argn], "-Throttle", strlen( argv[argn] ) ) == 0 && argn + 1 < argc )
+ {
+ do_throttle = 1;
+ throttle = atoi( argv[++argn] ) / 10.0;
+ }
+ else if ( strncmp( argv[argn], "-verbose", strlen( argv[argn] ) ) == 0 )
+ do_verbose = 1;
+ else if ( strncmp( argv[argn], "-timeout", strlen( argv[argn] ) ) == 0 && argn + 1 < argc )
+ idle_secs = atoi( argv[++argn] );
+ else if ( strncmp( argv[argn], "-jitter", strlen( argv[argn] ) ) == 0 )
+ do_jitter = 1;
+ else if ( strncmp( argv[argn], "-parallel", strlen( argv[argn] ) ) == 0 && argn + 1 < argc )
+ {
+ start = START_PARALLEL;
+ start_parallel = atoi( argv[++argn] );
+ if ( start_parallel < 1 )
+ {
+ (void) fprintf(
+ stderr, "%s: parallel must be at least 1\n", argv0 );
+ exit( 1 );
+ }
+ if ( start_parallel > max_connections )
+ {
+ (void) fprintf(
+ stderr, "%s: parallel may be at most %d\n", argv0, max_connections );
+ exit( 1 );
+ }
+ }
+ else if ( strncmp( argv[argn], "-rate", strlen( argv[argn] ) ) == 0 && argn + 1 < argc )
+ {
+ start = START_RATE;
+ start_rate = atoi( argv[++argn] );
+ if ( start_rate < 1 )
+ {
+ (void) fprintf(
+ stderr, "%s: rate must be at least 1\n", argv0 );
+ exit( 1 );
+ }
+ if ( start_rate > 1000 )
+ {
+ (void) fprintf(
+ stderr, "%s: rate may be at most 1000\n", argv0 );
+ exit( 1 );
+ }
+ }
+ else if ( strncmp( argv[argn], "-fetches", strlen( argv[argn] ) ) == 0 && argn + 1 < argc )
+ {
+ end = END_FETCHES;
+ end_fetches = atoi( argv[++argn] );
+ if ( end_fetches < 1 )
+ {
+ (void) fprintf(
+ stderr, "%s: fetches must be at least 1\n", argv0 );
+ exit( 1 );
+ }
+ }
+ else if ( strncmp( argv[argn], "-seconds", strlen( argv[argn] ) ) == 0 && argn + 1 < argc )
+ {
+ end = END_SECONDS;
+ end_seconds = atoi( argv[++argn] );
+ if ( end_seconds < 1 )
+ {
+ (void) fprintf(
+ stderr, "%s: seconds must be at least 1\n", argv0 );
+ exit( 1 );
+ }
+ }
+ else if ( strncmp( argv[argn], "-sip", strlen( argv[argn] ) ) == 0 && argn + 1 < argc )
+ sip_file = argv[++argn];
+#ifdef USE_SSL
+ else if ( strncmp( argv[argn], "-cipher", strlen( argv[argn] ) ) == 0 && argn + 1 < argc )
+ {
+ cipher = argv[++argn];
+ if ( strcasecmp( cipher, "fastsec" ) == 0 )
+ cipher = "RC4-MD5";
+ else if ( strcasecmp( cipher, "highsec" ) == 0 )
+ cipher = "DES-CBC3-SHA";
+ else if ( strcasecmp( cipher, "paranoid" ) == 0 )
+ cipher = "AES256-SHA";
+ }
+#endif /* USE_SSL */
+ else if ( strncmp( argv[argn], "-proxy", strlen( argv[argn] ) ) == 0 && argn + 1 < argc )
+ {
+ char* colon;
+ do_proxy = 1;
+ proxy_hostname = argv[++argn];
+ colon = strchr( proxy_hostname, ':' );
+ if ( colon == (char*) 0 )
+ proxy_port = 80;
+ else
+ {
+ proxy_port = (unsigned short) atoi( colon + 1 );
+ *colon = '\0';
+ }
+ }
+ else
+ usage();
+ ++argn;
+ }
+ if ( argn + 1 != argc )
+ usage();
+ if ( start == START_NONE || end == END_NONE )
+ usage();
+ if ( do_jitter && start != START_RATE )
+ usage();
+ url_file = argv[argn];
+
+ /* Read in and parse the URLs. */
+ read_url_file( url_file );
+
+ /* Read in the source IP file, if specified. */
+ if ( sip_file != (char*) 0 )
+ read_sip_file( sip_file );
+
+ /* Initialize the connections table. */
+ if ( start == START_PARALLEL )
+ max_connections = start_parallel;
+ connections = (connection*) malloc_check(
+ max_connections * sizeof(connection) );
+ for ( cnum = 0; cnum < max_connections; ++cnum )
+ connections[cnum].conn_state = CNST_FREE;
+ num_connections = max_parallel = 0;
+
+ /* Initialize the HTTP status-code histogram. */
+ for ( i = 0; i < 1000; ++i )
+ http_status_counts[i] = 0;
+
+ /* Initialize the statistics. */
+ fetches_started = 0;
+ connects_completed = 0;
+ responses_completed = 0;
+ fetches_completed = 0;
+ total_bytes = 0;
+ total_connect_usecs = 0;
+ max_connect_usecs = 0;
+ min_connect_usecs = 1000000000L;
+ total_response_usecs = 0;
+ max_response_usecs = 0;
+ min_response_usecs = 1000000000L;
+ total_timeouts = 0;
+ total_badbytes = 0;
+ total_badchecksums = 0;
+
+ /* Initialize the random number generator. */
+#ifdef HAVE_SRANDOMDEV
+ srandomdev();
+#else
+ srandom( (int) time( (time_t*) 0 ) ^ getpid() );
+#endif
+
+ /* Initialize the rest. */
+ tmr_init();
+ (void) gettimeofday( &now, (struct timezone*) 0 );
+ start_at = now;
+ if ( do_verbose )
+ (void) tmr_create(
+ &now, progress_report, JunkClientData, PROGRESS_SECS * 1000L, 1 );
+ if ( start == START_RATE )
+ {
+ start_interval = 1000L / start_rate;
+ if ( do_jitter )
+ {
+ low_interval = start_interval * 9 / 10;
+ high_interval = start_interval * 11 / 10;
+ range_interval = high_interval - low_interval + 1;
+ }
+ (void) tmr_create(
+ &now, start_timer, JunkClientData, start_interval, ! do_jitter );
+ }
+ if ( end == END_SECONDS )
+ (void) tmr_create(
+ &now, end_timer, JunkClientData, end_seconds * 1000L, 0 );
+ (void) signal( SIGPIPE, SIG_IGN );
+
+ /* Main loop. */
+ for (;;)
+ {
+ if ( end == END_FETCHES && fetches_completed >= end_fetches )
+ finish( &now );
+
+ if ( start == START_PARALLEL )
+ {
+ /* See if we need to start any new connections; but at most 10. */
+ for ( i = 0;
+ i < 10 &&
+ num_connections < start_parallel &&
+ ( end != END_FETCHES || fetches_started < end_fetches );
+ ++i )
+ {
+ start_connection( &now );
+ (void) gettimeofday( &now, (struct timezone*) 0 );
+ tmr_run( &now );
+ }
+ }
+
+ /* Build the fdsets. */
+ FD_ZERO( &rfdset );
+ FD_ZERO( &wfdset );
+ for ( cnum = 0; cnum < max_connections; ++cnum )
+ switch ( connections[cnum].conn_state )
+ {
+ case CNST_CONNECTING:
+ FD_SET( connections[cnum].conn_fd, &wfdset );
+ break;
+ case CNST_HEADERS:
+ case CNST_READING:
+ FD_SET( connections[cnum].conn_fd, &rfdset );
+ break;
+ }
+ r = select(
+ FD_SETSIZE, &rfdset, &wfdset, (fd_set*) 0, tmr_timeout( &now ) );
+ if ( r < 0 )
+ {
+ perror( "select" );
+ exit( 1 );
+ }
+ (void) gettimeofday( &now, (struct timezone*) 0 );
+
+ /* Service them. */
+ for ( cnum = 0; cnum < max_connections; ++cnum )
+ switch ( connections[cnum].conn_state )
+ {
+ case CNST_CONNECTING:
+ if ( FD_ISSET( connections[cnum].conn_fd, &wfdset ) )
+ handle_connect( cnum, &now, 1 );
+ break;
+ case CNST_HEADERS:
+ case CNST_READING:
+ if ( FD_ISSET( connections[cnum].conn_fd, &rfdset ) )
+ handle_read( cnum, &now );
+ break;
+ }
+ /* And run the timers. */
+ tmr_run( &now );
+ }
+
+ /* NOT_REACHED */
+ }
+
+
+static void
+usage( void )
+ {
+ (void) fprintf( stderr,
+ "usage: %s [-checksum] [-throttle] [-proxy host:port] [-verbose] [-timeout secs] [-sip sip_file]\n", argv0 );
+#ifdef USE_SSL
+ (void) fprintf( stderr,
+ " [-cipher str]\n" );
+#endif /* USE_SSL */
+ (void) fprintf( stderr,
+ " -parallel N | -rate N [-jitter]\n" );
+ (void) fprintf( stderr,
+ " -fetches N | -seconds N\n" );
+ (void) fprintf( stderr,
+ " url_file\n" );
+ (void) fprintf( stderr,
+ "One start specifier, either -parallel or -rate, is required.\n" );
+ (void) fprintf( stderr,
+ "One end specifier, either -fetches or -seconds, is required.\n" );
+ exit( 1 );
+ }
+
+
+static void
+read_url_file( char* url_file )
+ {
+ FILE* fp;
+ char line[5000], hostname[5000];
+ char* http = "http://";
+ int http_len = strlen( http );
+#ifdef USE_SSL
+ char* https = "https://";
+ int https_len = strlen( https );
+#endif
+ int proto_len, host_len;
+ char* cp;
+
+ fp = fopen( url_file, "r" );
+ if ( fp == (FILE*) 0 )
+ {
+ perror( url_file );
+ exit( 1 );
+ }
+
+ max_urls = 100;
+ urls = (url*) malloc_check( max_urls * sizeof(url) );
+ num_urls = 0;
+ while ( fgets( line, sizeof(line), fp ) != (char*) 0 )
+ {
+ /* Nuke trailing newline. */
+ if ( line[strlen( line ) - 1] == '\n' )
+ line[strlen( line ) - 1] = '\0';
+
+ /* Check for room in urls. */
+ if ( num_urls >= max_urls )
+ {
+ max_urls *= 2;
+ urls = (url*) realloc_check( (void*) urls, max_urls * sizeof(url) );
+ }
+
+ /* Add to table. */
+ urls[num_urls].url_str = strdup_check( line );
+
+ /* Parse it. */
+ if ( strncmp( http, line, http_len ) == 0 )
+ {
+ proto_len = http_len;
+ urls[num_urls].protocol = PROTO_HTTP;
+ }
+#ifdef USE_SSL
+ else if ( strncmp( https, line, https_len ) == 0 )
+ {
+ proto_len = https_len;
+ urls[num_urls].protocol = PROTO_HTTPS;
+ }
+#endif
+ else
+ {
+ (void) fprintf( stderr, "%s: unknown protocol - %s\n", argv0, line );
+ exit( 1 );
+ }
+ for ( cp = line + proto_len;
+ *cp != '\0' && *cp != ':' && *cp != '/'; ++cp )
+ ;
+ host_len = cp - line;
+ host_len -= proto_len;
+ strncpy( hostname, line + proto_len, host_len );
+ hostname[host_len] = '\0';
+ urls[num_urls].hostname = strdup_check( hostname );
+ if ( *cp == ':' )
+ {
+ urls[num_urls].port = (unsigned short) atoi( ++cp );
+ while ( *cp != '\0' && *cp != '/' )
+ ++cp;
+ }
+ else
+#ifdef USE_SSL
+ if ( urls[num_urls].protocol == PROTO_HTTPS )
+ urls[num_urls].port = 443;
+ else
+ urls[num_urls].port = 80;
+#else
+ urls[num_urls].port = 80;
+#endif
+ if ( *cp == '\0' )
+ urls[num_urls].filename = strdup_check( "/" );
+ else
+ urls[num_urls].filename = strdup_check( cp );
+
+ lookup_address( num_urls );
+
+ urls[num_urls].got_bytes = 0;
+ urls[num_urls].got_checksum = 0;
+ ++num_urls;
+ }
+ }
+
+
+static void
+lookup_address( int url_num )
+ {
+ char* hostname;
+ unsigned short port;
+#ifdef USE_IPV6
+ struct addrinfo hints;
+ char portstr[10];
+ int gaierr;
+ struct addrinfo* ai;
+ struct addrinfo* ai2;
+ struct addrinfo* aiv4;
+ struct addrinfo* aiv6;
+#else /* USE_IPV6 */
+ struct hostent *he;
+#endif /* USE_IPV6 */
+
+ urls[url_num].sa_len = sizeof(urls[url_num].sa);
+ (void) memset( (void*) &urls[url_num].sa, 0, urls[url_num].sa_len );
+
+ if ( do_proxy )
+ {
+ hostname = proxy_hostname;
+ port = proxy_port;
+ }
+ else
+ {
+ hostname = urls[url_num].hostname;
+ port = urls[url_num].port;
+ }
+
+#ifdef USE_IPV6
+
+ (void) memset( &hints, 0, sizeof(hints) );
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ (void) snprintf( portstr, sizeof(portstr), "%d", (int) port );
+ if ( (gaierr = getaddrinfo( hostname, portstr, &hints, &ai )) != 0 )
+ {
+ (void) fprintf(
+ stderr, "%s: getaddrinfo %s - %s\n", argv0, hostname,
+ gai_strerror( gaierr ) );
+ exit( 1 );
+ }
+
+ /* Find the first IPv4 and IPv6 entries. */
+ aiv4 = (struct addrinfo*) 0;
+ aiv6 = (struct addrinfo*) 0;
+ for ( ai2 = ai; ai2 != (struct addrinfo*) 0; ai2 = ai2->ai_next )
+ {
+ switch ( ai2->ai_family )
+ {
+ case AF_INET:
+ if ( aiv4 == (struct addrinfo*) 0 )
+ aiv4 = ai2;
+ break;
+ case AF_INET6:
+ if ( aiv6 == (struct addrinfo*) 0 )
+ aiv6 = ai2;
+ break;
+ }
+ }
+
+ /* If there's an IPv4 address, use that, otherwise try IPv6. */
+ if ( aiv4 != (struct addrinfo*) 0 )
+ {
+ if ( sizeof(urls[url_num].sa) < aiv4->ai_addrlen )
+ {
+ (void) fprintf(
+ stderr, "%s - sockaddr too small (%lu < %lu)\n", hostname,
+ (unsigned long) sizeof(urls[url_num].sa),
+ (unsigned long) aiv4->ai_addrlen );
+ exit( 1 );
+ }
+ urls[url_num].sock_family = aiv4->ai_family;
+ urls[url_num].sock_type = aiv4->ai_socktype;
+ urls[url_num].sock_protocol = aiv4->ai_protocol;
+ urls[url_num].sa_len = aiv4->ai_addrlen;
+ (void) memmove( &urls[url_num].sa, aiv4->ai_addr, aiv4->ai_addrlen );
+ freeaddrinfo( ai );
+ return;
+ }
+ if ( aiv6 != (struct addrinfo*) 0 )
+ {
+ if ( sizeof(urls[url_num].sa) < aiv6->ai_addrlen )
+ {
+ (void) fprintf(
+ stderr, "%s - sockaddr too small (%lu < %lu)\n", hostname,
+ (unsigned long) sizeof(urls[url_num].sa),
+ (unsigned long) aiv6->ai_addrlen );
+ exit( 1 );
+ }
+ urls[url_num].sock_family = aiv6->ai_family;
+ urls[url_num].sock_type = aiv6->ai_socktype;
+ urls[url_num].sock_protocol = aiv6->ai_protocol;
+ urls[url_num].sa_len = aiv6->ai_addrlen;
+ (void) memmove( &urls[url_num].sa, aiv6->ai_addr, aiv6->ai_addrlen );
+ freeaddrinfo( ai );
+ return;
+ }
+
+ (void) fprintf(
+ stderr, "%s: no valid address found for host %s\n", argv0, hostname );
+ exit( 1 );
+
+#else /* USE_IPV6 */
+
+ he = gethostbyname( hostname );
+ if ( he == (struct hostent*) 0 )
+ {
+ (void) fprintf( stderr, "%s: unknown host - %s\n", argv0, hostname );
+ exit( 1 );
+ }
+ urls[url_num].sock_family = urls[url_num].sa.sin_family = he->h_addrtype;
+ urls[url_num].sock_type = SOCK_STREAM;
+ urls[url_num].sock_protocol = 0;
+ urls[url_num].sa_len = sizeof(urls[url_num].sa);
+ (void) memmove( &urls[url_num].sa.sin_addr, he->h_addr, he->h_length );
+ urls[url_num].sa.sin_port = htons( port );
+
+#endif /* USE_IPV6 */
+
+ }
+
+
+static void
+read_sip_file( char* sip_file )
+ {
+ FILE* fp;
+ char line[5000];
+
+ fp = fopen( sip_file, "r" );
+ if ( fp == (FILE*) 0 )
+ {
+ perror( sip_file );
+ exit( 1 );
+ }
+
+ max_sips = 100;
+ sips = (sip*) malloc_check( max_sips * sizeof(sip) );
+ num_sips = 0;
+ while ( fgets( line, sizeof(line), fp ) != (char*) 0 )
+ {
+ /* Nuke trailing newline. */
+ if ( line[strlen( line ) - 1] == '\n' )
+ line[strlen( line ) - 1] = '\0';
+
+ /* Check for room in sips. */
+ if ( num_sips >= max_sips )
+ {
+ max_sips *= 2;
+ sips = (sip*) realloc_check( (void*) sips, max_sips * sizeof(sip) );
+ }
+
+ /* Add to table. */
+ sips[num_sips].str = strdup_check( line );
+ (void) memset( (void*) &sips[num_sips].sa, 0, sizeof(sips[num_sips].sa) );
+ if ( ! inet_aton( sips[num_sips].str, &sips[num_sips].sa.sin_addr ) )
+ {
+ (void) fprintf(
+ stderr, "%s: cannot convert source IP address %s\n",
+ argv0, sips[num_sips].str );
+ exit( 1 );
+ }
+ ++num_sips;
+ }
+ }
+
+
+static void
+start_connection( struct timeval* nowP )
+ {
+ int cnum, url_num;
+
+ /* Find an empty connection slot. */
+ for ( cnum = 0; cnum < max_connections; ++cnum )
+ if ( connections[cnum].conn_state == CNST_FREE )
+ {
+ /* Choose a URL. */
+ url_num = ( (unsigned long) random() ) % ( (unsigned int) num_urls );
+ /* Start the socket. */
+ start_socket( url_num, cnum, nowP );
+ if ( connections[cnum].conn_state != CNST_FREE )
+ {
+ ++num_connections;
+ if ( num_connections > max_parallel )
+ max_parallel = num_connections;
+ }
+ ++fetches_started;
+ return;
+ }
+ /* No slots left. */
+ (void) fprintf( stderr, "%s: ran out of connection slots\n", argv0 );
+ finish( nowP );
+ }
+
+
+static void
+start_socket( int url_num, int cnum, struct timeval* nowP )
+ {
+ ClientData client_data;
+ int flags;
+ int sip_num;
+
+ /* Start filling in the connection slot. */
+ connections[cnum].url_num = url_num;
+ connections[cnum].started_at = *nowP;
+ client_data.i = cnum;
+ connections[cnum].did_connect = 0;
+ connections[cnum].did_response = 0;
+ connections[cnum].idle_timer = tmr_create(
+ nowP, idle_connection, client_data, idle_secs * 1000L, 0 );
+ connections[cnum].wakeup_timer = (Timer*) 0;
+ connections[cnum].content_length = -1;
+ connections[cnum].bytes = 0;
+ connections[cnum].checksum = 0;
+ connections[cnum].http_status = -1;
+
+ /* Make a socket. */
+ connections[cnum].conn_fd = socket(
+ urls[url_num].sock_family, urls[url_num].sock_type,
+ urls[url_num].sock_protocol );
+ if ( connections[cnum].conn_fd < 0 )
+ {
+ perror( urls[url_num].url_str );
+ return;
+ }
+
+ /* Set the file descriptor to no-delay mode. */
+ flags = fcntl( connections[cnum].conn_fd, F_GETFL, 0 );
+ if ( flags == -1 )
+ {
+ perror( urls[url_num].url_str );
+ (void) close( connections[cnum].conn_fd );
+ return;
+ }
+ if ( fcntl( connections[cnum].conn_fd, F_SETFL, flags | O_NDELAY ) < 0 )
+ {
+ perror( urls[url_num].url_str );
+ (void) close( connections[cnum].conn_fd );
+ return;
+ }
+
+ if ( num_sips > 0 )
+ {
+ /* Try a random source IP address. */
+ sip_num = ( (unsigned long) random() ) % ( (unsigned int) num_sips );
+ if ( bind(
+ connections[cnum].conn_fd,
+ (struct sockaddr*) &sips[sip_num].sa,
+ sizeof(sips[sip_num].sa) ) < 0 )
+ {
+ perror( "binding local address" );
+ (void) close( connections[cnum].conn_fd );
+ return;
+ }
+ }
+
+ /* Connect to the host. */
+ connections[cnum].sa_len = urls[url_num].sa_len;
+ (void) memmove(
+ (void*) &connections[cnum].sa, (void*) &urls[url_num].sa,
+ urls[url_num].sa_len );
+ connections[cnum].connect_at = *nowP;
+ if ( connect(
+ connections[cnum].conn_fd,
+ (struct sockaddr*) &connections[cnum].sa,
+ connections[cnum].sa_len ) < 0 )
+ {
+ if ( errno == EINPROGRESS )
+ {
+ connections[cnum].conn_state = CNST_CONNECTING;
+ return;
+ }
+ else
+ {
+ perror( urls[url_num].url_str );
+ (void) close( connections[cnum].conn_fd );
+ return;
+ }
+ }
+
+ /* Connect succeeded instantly, so handle it now. */
+ (void) gettimeofday( nowP, (struct timezone*) 0 );
+ handle_connect( cnum, nowP, 0 );
+ }
+
+
+static void
+handle_connect( int cnum, struct timeval* nowP, int double_check )
+ {
+ int url_num;
+ char buf[600];
+ int bytes, r;
+
+ url_num = connections[cnum].url_num;
+ if ( double_check )
+ {
+ /* Check to make sure the non-blocking connect succeeded. */
+ int err, errlen;
+
+ if ( connect(
+ connections[cnum].conn_fd,
+ (struct sockaddr*) &connections[cnum].sa,
+ connections[cnum].sa_len ) < 0 )
+ {
+ switch ( errno )
+ {
+ case EISCONN:
+ /* Ok! */
+ break;
+ case EINVAL:
+ errlen = sizeof(err);
+ if ( getsockopt( connections[cnum].conn_fd, SOL_SOCKET, SO_ERROR, (void*) &err, &errlen ) < 0 )
+ (void) fprintf(
+ stderr, "%s: unknown connect error\n",
+ urls[url_num].url_str );
+ else
+ (void) fprintf(
+ stderr, "%s: %s\n", urls[url_num].url_str,
+ strerror( err ) );
+ close_connection( cnum );
+ return;
+ default:
+ perror( urls[url_num].url_str );
+ close_connection( cnum );
+ return;
+ }
+ }
+ }
+#ifdef USE_SSL
+ if ( urls[url_num].protocol == PROTO_HTTPS )
+ {
+ int flags;
+
+ /* Make SSL connection. */
+ if ( ssl_ctx == (SSL_CTX*) 0 )
+ {
+ SSL_load_error_strings();
+ SSLeay_add_ssl_algorithms();
+ ssl_ctx = SSL_CTX_new( SSLv23_client_method() );
+ if ( cipher != (char*) 0 )
+ {
+ if ( ! SSL_CTX_set_cipher_list( ssl_ctx, cipher ) )
+ {
+ (void) fprintf(
+ stderr, "%s: cannot set cipher list\n", argv0 );
+ ERR_print_errors_fp( stderr );
+ close_connection( cnum );
+ return;
+ }
+ }
+ }
+ if ( ! RAND_status() )
+ {
+ unsigned char bytes[1024];
+ int i;
+ for ( i = 0; i < sizeof(bytes); ++i )
+ bytes[i] = random() % 0xff;
+ RAND_seed( bytes, sizeof(bytes) );
+ }
+ flags = fcntl( connections[cnum].conn_fd, F_GETFL, 0 );
+ if ( flags != -1 )
+ (void) fcntl(
+ connections[cnum].conn_fd, F_SETFL, flags & ~ (int) O_NDELAY );
+ connections[cnum].ssl = SSL_new( ssl_ctx );
+ SSL_set_fd( connections[cnum].ssl, connections[cnum].conn_fd );
+ r = SSL_connect( connections[cnum].ssl );
+ if ( r <= 0 )
+ {
+ (void) fprintf(
+ stderr, "%s: SSL connection failed - %d\n", argv0, r );
+ ERR_print_errors_fp( stderr );
+ close_connection( cnum );
+ return;
+ }
+ }
+#endif
+ connections[cnum].did_connect = 1;
+
+ /* Format the request. */
+ if ( do_proxy )
+ {
+#ifdef USE_SSL
+ bytes = snprintf(
+ buf, sizeof(buf), "GET %s://%.500s:%d%.500s HTTP/1.0\r\n",
+ urls[url_num].protocol == PROTO_HTTPS ? "https" : "http",
+ urls[url_num].hostname, (int) urls[url_num].port,
+ urls[url_num].filename );
+#else
+ bytes = snprintf(
+ buf, sizeof(buf), "GET http://%.500s:%d%.500s HTTP/1.0\r\n",
+ urls[url_num].hostname, (int) urls[url_num].port,
+ urls[url_num].filename );
+#endif
+ }
+ else
+ bytes = snprintf(
+ buf, sizeof(buf), "GET %.500s HTTP/1.0\r\n",
+ urls[url_num].filename );
+ bytes += snprintf(
+ &buf[bytes], sizeof(buf) - bytes, "Host: %s\r\n",
+ urls[url_num].hostname );
+ bytes += snprintf(
+ &buf[bytes], sizeof(buf) - bytes, "User-Agent: %s\r\n", VERSION );
+ bytes += snprintf( &buf[bytes], sizeof(buf) - bytes, "\r\n" );
+
+ /* Send the request. */
+ connections[cnum].request_at = *nowP;
+#ifdef USE_SSL
+ if ( urls[url_num].protocol == PROTO_HTTPS )
+ r = SSL_write( connections[cnum].ssl, buf, bytes );
+ else
+ r = write( connections[cnum].conn_fd, buf, bytes );
+#else
+ r = write( connections[cnum].conn_fd, buf, bytes );
+#endif
+ if ( r < 0 )
+ {
+ perror( urls[url_num].url_str );
+ close_connection( cnum );
+ return;
+ }
+ connections[cnum].conn_state = CNST_HEADERS;
+ connections[cnum].header_state = HDST_LINE1_PROTOCOL;
+ }
+
+
+static void
+handle_read( int cnum, struct timeval* nowP )
+ {
+ char buf[30000]; /* must be larger than throttle / 2 */
+ int bytes_to_read, bytes_read, bytes_handled;
+ float elapsed;
+ ClientData client_data;
+ register long checksum;
+
+ tmr_reset( nowP, connections[cnum].idle_timer );
+
+ if ( do_throttle )
+ bytes_to_read = throttle / 2.0;
+ else
+ bytes_to_read = sizeof(buf);
+ if ( ! connections[cnum].did_response )
+ {
+ connections[cnum].did_response = 1;
+ connections[cnum].response_at = *nowP;
+ }
+#ifdef USE_SSL
+ if ( urls[connections[cnum].url_num].protocol == PROTO_HTTPS )
+ bytes_read = SSL_read( connections[cnum].ssl, buf, bytes_to_read );
+ else
+ bytes_read = read( connections[cnum].conn_fd, buf, bytes_to_read );
+#else
+ bytes_read = read( connections[cnum].conn_fd, buf, bytes_to_read );
+#endif
+ if ( bytes_read <= 0 )
+ {
+ close_connection( cnum );
+ return;
+ }
+
+ for ( bytes_handled = 0; bytes_handled < bytes_read; )
+ {
+ switch ( connections[cnum].conn_state )
+ {
+ case CNST_HEADERS:
+ /* State machine to read until we reach the file part. Looks for
+ ** Content-Length header too.
+ */
+ for ( ; bytes_handled < bytes_read && connections[cnum].conn_state == CNST_HEADERS; ++bytes_handled )
+ {
+ switch ( connections[cnum].header_state )
+ {
+
+ case HDST_LINE1_PROTOCOL:
+ switch ( buf[bytes_handled] )
+ {
+ case ' ': case '\t':
+ connections[cnum].header_state = HDST_LINE1_WHITESPACE;
+ break;
+ case '\n':
+ connections[cnum].header_state = HDST_LF;
+ break;
+ case '\r':
+ connections[cnum].header_state = HDST_CR;
+ break;
+ }
+ break;
+
+ case HDST_LINE1_WHITESPACE:
+ switch ( buf[bytes_handled] )
+ {
+ case ' ': case '\t':
+ break;
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ connections[cnum].http_status =
+ buf[bytes_handled] - '0';
+ connections[cnum].header_state = HDST_LINE1_STATUS;
+ break;
+ case '\n':
+ connections[cnum].header_state = HDST_LF;
+ break;
+ case '\r':
+ connections[cnum].header_state = HDST_CR;
+ break;
+ default:
+ connections[cnum].header_state = HDST_TEXT;
+ break;
+ }
+ break;
+
+ case HDST_LINE1_STATUS:
+ switch ( buf[bytes_handled] )
+ {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ connections[cnum].http_status =
+ connections[cnum].http_status * 10 +
+ buf[bytes_handled] - '0';
+ break;
+ case '\n':
+ connections[cnum].header_state = HDST_LF;
+ break;
+ case '\r':
+ connections[cnum].header_state = HDST_CR;
+ break;
+ default:
+ connections[cnum].header_state = HDST_TEXT;
+ break;
+ }
+ break;
+
+ case HDST_BOL:
+ switch ( buf[bytes_handled] )
+ {
+ case '\n':
+ connections[cnum].header_state = HDST_LF;
+ break;
+ case '\r':
+ connections[cnum].header_state = HDST_CR;
+ break;
+ case 'C': case 'c':
+ connections[cnum].header_state = HDST_C;
+ break;
+ default:
+ connections[cnum].header_state = HDST_TEXT;
+ break;
+ }
+ break;
+
+ case HDST_TEXT:
+ switch ( buf[bytes_handled] )
+ {
+ case '\n':
+ connections[cnum].header_state = HDST_LF;
+ break;
+ case '\r':
+ connections[cnum].header_state = HDST_CR;
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case HDST_LF:
+ switch ( buf[bytes_handled] )
+ {
+ case '\n':
+ connections[cnum].conn_state = CNST_READING;
+ break;
+ case '\r':
+ connections[cnum].header_state = HDST_CR;
+ break;
+ case 'C': case 'c':
+ connections[cnum].header_state = HDST_C;
+ break;
+ default:
+ connections[cnum].header_state = HDST_TEXT;
+ break;
+ }
+ break;
+
+ case HDST_CR:
+ switch ( buf[bytes_handled] )
+ {
+ case '\n':
+ connections[cnum].header_state = HDST_CRLF;
+ break;
+ case '\r':
+ connections[cnum].conn_state = CNST_READING;
+ break;
+ case 'C': case 'c':
+ connections[cnum].header_state = HDST_C;
+ break;
+ default:
+ connections[cnum].header_state = HDST_TEXT;
+ break;
+ }
+ break;
+
+ case HDST_CRLF:
+ switch ( buf[bytes_handled] )
+ {
+ case '\n':
+ connections[cnum].conn_state = CNST_READING;
+ break;
+ case '\r':
+ connections[cnum].header_state = HDST_CRLFCR;
+ break;
+ case 'C': case 'c':
+ connections[cnum].header_state = HDST_C;
+ break;
+ default:
+ connections[cnum].header_state = HDST_TEXT;
+ break;
+ }
+ break;
+
+ case HDST_CRLFCR:
+ switch ( buf[bytes_handled] )
+ {
+ case '\n': case '\r':
+ connections[cnum].conn_state = CNST_READING;
+ break;
+ case 'C': case 'c':
+ connections[cnum].header_state = HDST_C;
+ break;
+ default:
+ connections[cnum].header_state = HDST_TEXT;
+ break;
+ }
+ break;
+
+ case HDST_C:
+ switch ( buf[bytes_handled] )
+ {
+ case 'O': case 'o':
+ connections[cnum].header_state = HDST_CO;
+ break;
+ case '\n':
+ connections[cnum].header_state = HDST_LF;
+ break;
+ case '\r':
+ connections[cnum].header_state = HDST_CR;
+ break;
+ default:
+ connections[cnum].header_state = HDST_TEXT;
+ break;
+ }
+ break;
+
+ case HDST_CO:
+ switch ( buf[bytes_handled] )
+ {
+ case 'N': case 'n':
+ connections[cnum].header_state = HDST_CON;
+ break;
+ case '\n':
+ connections[cnum].header_state = HDST_LF;
+ break;
+ case '\r':
+ connections[cnum].header_state = HDST_CR;
+ break;
+ default:
+ connections[cnum].header_state = HDST_TEXT;
+ break;
+ }
+ break;
+
+ case HDST_CON:
+ switch ( buf[bytes_handled] )
+ {
+ case 'T': case 't':
+ connections[cnum].header_state = HDST_CONT;
+ break;
+ case '\n':
+ connections[cnum].header_state = HDST_LF;
+ break;
+ case '\r':
+ connections[cnum].header_state = HDST_CR;
+ break;
+ default:
+ connections[cnum].header_state = HDST_TEXT;
+ break;
+ }
+ break;
+
+ case HDST_CONT:
+ switch ( buf[bytes_handled] )
+ {
+ case 'E': case 'e':
+ connections[cnum].header_state = HDST_CONTE;
+ break;
+ case '\n':
+ connections[cnum].header_state = HDST_LF;
+ break;
+ case '\r':
+ connections[cnum].header_state = HDST_CR;
+ break;
+ default:
+ connections[cnum].header_state = HDST_TEXT;
+ break;
+ }
+ break;
+
+ case HDST_CONTE:
+ switch ( buf[bytes_handled] )
+ {
+ case 'N': case 'n':
+ connections[cnum].header_state = HDST_CONTEN;
+ break;
+ case '\n':
+ connections[cnum].header_state = HDST_LF;
+ break;
+ case '\r':
+ connections[cnum].header_state = HDST_CR;
+ break;
+ default:
+ connections[cnum].header_state = HDST_TEXT;
+ break;
+ }
+ break;
+
+ case HDST_CONTEN:
+ switch ( buf[bytes_handled] )
+ {
+ case 'T': case 't':
+ connections[cnum].header_state = HDST_CONTENT;
+ break;
+ case '\n':
+ connections[cnum].header_state = HDST_LF;
+ break;
+ case '\r':
+ connections[cnum].header_state = HDST_CR;
+ break;
+ default:
+ connections[cnum].header_state = HDST_TEXT;
+ break;
+ }
+ break;
+
+ case HDST_CONTENT:
+ switch ( buf[bytes_handled] )
+ {
+ case '-':
+ connections[cnum].header_state = HDST_CONTENT_;
+ break;
+ case '\n':
+ connections[cnum].header_state = HDST_LF;
+ break;
+ case '\r':
+ connections[cnum].header_state = HDST_CR;
+ break;
+ default:
+ connections[cnum].header_state = HDST_TEXT;
+ break;
+ }
+ break;
+
+ case HDST_CONTENT_:
+ switch ( buf[bytes_handled] )
+ {
+ case 'L': case 'l':
+ connections[cnum].header_state = HDST_CONTENT_L;
+ break;
+ case '\n':
+ connections[cnum].header_state = HDST_LF;
+ break;
+ case '\r':
+ connections[cnum].header_state = HDST_CR;
+ break;
+ default:
+ connections[cnum].header_state = HDST_TEXT;
+ break;
+ }
+ break;
+
+ case HDST_CONTENT_L:
+ switch ( buf[bytes_handled] )
+ {
+ case 'E': case 'e':
+ connections[cnum].header_state = HDST_CONTENT_LE;
+ break;
+ case '\n':
+ connections[cnum].header_state = HDST_LF;
+ break;
+ case '\r':
+ connections[cnum].header_state = HDST_CR;
+ break;
+ default:
+ connections[cnum].header_state = HDST_TEXT;
+ break;
+ }
+ break;
+
+ case HDST_CONTENT_LE:
+ switch ( buf[bytes_handled] )
+ {
+ case 'N': case 'n':
+ connections[cnum].header_state = HDST_CONTENT_LEN;
+ break;
+ case '\n':
+ connections[cnum].header_state = HDST_LF;
+ break;
+ case '\r':
+ connections[cnum].header_state = HDST_CR;
+ break;
+ default:
+ connections[cnum].header_state = HDST_TEXT;
+ break;
+ }
+ break;
+
+ case HDST_CONTENT_LEN:
+ switch ( buf[bytes_handled] )
+ {
+ case 'G': case 'g':
+ connections[cnum].header_state = HDST_CONTENT_LENG;
+ break;
+ case '\n':
+ connections[cnum].header_state = HDST_LF;
+ break;
+ case '\r':
+ connections[cnum].header_state = HDST_CR;
+ break;
+ default:
+ connections[cnum].header_state = HDST_TEXT;
+ break;
+ }
+ break;
+
+ case HDST_CONTENT_LENG:
+ switch ( buf[bytes_handled] )
+ {
+ case 'T': case 't':
+ connections[cnum].header_state = HDST_CONTENT_LENGT;
+ break;
+ case '\n':
+ connections[cnum].header_state = HDST_LF;
+ break;
+ case '\r':
+ connections[cnum].header_state = HDST_CR;
+ break;
+ default:
+ connections[cnum].header_state = HDST_TEXT;
+ break;
+ }
+ break;
+
+ case HDST_CONTENT_LENGT:
+ switch ( buf[bytes_handled] )
+ {
+ case 'H': case 'h':
+ connections[cnum].header_state = HDST_CONTENT_LENGTH;
+ break;
+ case '\n':
+ connections[cnum].header_state = HDST_LF;
+ break;
+ case '\r':
+ connections[cnum].header_state = HDST_CR;
+ break;
+ default:
+ connections[cnum].header_state = HDST_TEXT;
+ break;
+ }
+ break;
+
+ case HDST_CONTENT_LENGTH:
+ switch ( buf[bytes_handled] )
+ {
+ case ':':
+ connections[cnum].header_state = HDST_CONTENT_LENGTH_COLON;
+ break;
+ case '\n':
+ connections[cnum].header_state = HDST_LF;
+ break;
+ case '\r':
+ connections[cnum].header_state = HDST_CR;
+ break;
+ default:
+ connections[cnum].header_state = HDST_TEXT;
+ break;
+ }
+ break;
+
+ case HDST_CONTENT_LENGTH_COLON:
+ switch ( buf[bytes_handled] )
+ {
+ case ' ': case '\t':
+ connections[cnum].header_state = HDST_CONTENT_LENGTH_COLON_WHITESPACE;
+ break;
+ case '\n':
+ connections[cnum].header_state = HDST_LF;
+ break;
+ case '\r':
+ connections[cnum].header_state = HDST_CR;
+ break;
+ default:
+ connections[cnum].header_state = HDST_TEXT;
+ break;
+ }
+ break;
+
+ case HDST_CONTENT_LENGTH_COLON_WHITESPACE:
+ switch ( buf[bytes_handled] )
+ {
+ case ' ': case '\t':
+ break;
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ connections[cnum].content_length = buf[bytes_handled] - '0';
+ connections[cnum].header_state = HDST_CONTENT_LENGTH_COLON_WHITESPACE_NUM;
+ break;
+ case '\n':
+ connections[cnum].header_state = HDST_LF;
+ break;
+ case '\r':
+ connections[cnum].header_state = HDST_CR;
+ break;
+ default:
+ connections[cnum].header_state = HDST_TEXT;
+ break;
+ }
+ break;
+
+ case HDST_CONTENT_LENGTH_COLON_WHITESPACE_NUM:
+ switch ( buf[bytes_handled] )
+ {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ connections[cnum].content_length =
+ connections[cnum].content_length * 10 +
+ buf[bytes_handled] - '0';
+ break;
+ case '\n':
+ connections[cnum].header_state = HDST_LF;
+ break;
+ case '\r':
+ connections[cnum].header_state = HDST_CR;
+ break;
+ default:
+ connections[cnum].header_state = HDST_TEXT;
+ break;
+ }
+ break;
+
+ }
+ }
+ break;
+
+ case CNST_READING:
+ connections[cnum].bytes += bytes_read - bytes_handled;
+ if ( do_throttle )
+ {
+ /* Check if we're reading too fast. */
+ elapsed = delta_timeval( &connections[cnum].started_at, nowP ) / 1000000.0;
+ if ( elapsed > 0.01 && connections[cnum].bytes / elapsed > throttle )
+ {
+ connections[cnum].conn_state = CNST_PAUSING;
+ client_data.i = cnum;
+ connections[cnum].wakeup_timer = tmr_create(
+ nowP, wakeup_connection, client_data, 1000L, 0 );
+ }
+ }
+ if ( do_checksum )
+ {
+ checksum = connections[cnum].checksum;
+ for ( ; bytes_handled < bytes_read; ++bytes_handled )
+ {
+ if ( checksum & 1 )
+ checksum = ( checksum >> 1 ) + 0x8000;
+ else
+ checksum >>= 1;
+ checksum += buf[bytes_handled];
+ checksum &= 0xffff;
+ }
+ connections[cnum].checksum = checksum;
+ }
+ else
+ bytes_handled = bytes_read;
+
+ if ( connections[cnum].content_length != -1 &&
+ connections[cnum].bytes >= connections[cnum].content_length )
+ {
+ close_connection( cnum );
+ return;
+ }
+
+ break;
+ }
+ }
+ }
+
+
+static void
+idle_connection( ClientData client_data, struct timeval* nowP )
+ {
+ int cnum;
+
+ cnum = client_data.i;
+ connections[cnum].idle_timer = (Timer*) 0;
+ (void) fprintf(
+ stderr, "%s: timed out\n", urls[connections[cnum].url_num].url_str );
+ close_connection( cnum );
+ ++total_timeouts;
+ }
+
+
+static void
+wakeup_connection( ClientData client_data, struct timeval* nowP )
+ {
+ int cnum;
+
+ cnum = client_data.i;
+ connections[cnum].wakeup_timer = (Timer*) 0;
+ connections[cnum].conn_state = CNST_READING;
+ }
+
+
+static void
+close_connection( int cnum )
+ {
+ int url_num;
+
+#ifdef USE_SSL
+ if ( urls[connections[cnum].url_num].protocol == PROTO_HTTPS )
+ SSL_free( connections[cnum].ssl );
+#endif
+ (void) close( connections[cnum].conn_fd );
+ connections[cnum].conn_state = CNST_FREE;
+ if ( connections[cnum].idle_timer != (Timer*) 0 )
+ tmr_cancel( connections[cnum].idle_timer );
+ if ( connections[cnum].wakeup_timer != (Timer*) 0 )
+ tmr_cancel( connections[cnum].wakeup_timer );
+ --num_connections;
+ ++fetches_completed;
+ total_bytes += connections[cnum].bytes;
+ if ( connections[cnum].did_connect )
+ {
+ long long connect_usecs = delta_timeval(
+ &connections[cnum].connect_at, &connections[cnum].request_at );
+ total_connect_usecs += connect_usecs;
+ max_connect_usecs = max( max_connect_usecs, connect_usecs );
+ min_connect_usecs = min( min_connect_usecs, connect_usecs );
+ ++connects_completed;
+ }
+ if ( connections[cnum].did_response )
+ {
+ long long response_usecs = delta_timeval(
+ &connections[cnum].request_at, &connections[cnum].response_at );
+ total_response_usecs += response_usecs;
+ max_response_usecs = max( max_response_usecs, response_usecs );
+ min_response_usecs = min( min_response_usecs, response_usecs );
+ ++responses_completed;
+ }
+ if ( connections[cnum].http_status >= 0 && connections[cnum].http_status <= 999 )
+ ++http_status_counts[connections[cnum].http_status];
+
+ url_num = connections[cnum].url_num;
+ if ( do_checksum )
+ {
+ if ( ! urls[url_num].got_checksum )
+ {
+ urls[url_num].checksum = connections[cnum].checksum;
+ urls[url_num].got_checksum = 1;
+ }
+ else
+ {
+ if ( connections[cnum].checksum != urls[url_num].checksum )
+ {
+ (void) fprintf(
+ stderr, "%s: checksum wrong\n", urls[url_num].url_str );
+ ++total_badchecksums;
+ }
+ }
+ }
+ else
+ {
+ if ( ! urls[url_num].got_bytes )
+ {
+ urls[url_num].bytes = connections[cnum].bytes;
+ urls[url_num].got_bytes = 1;
+ }
+ else
+ {
+ if ( connections[cnum].bytes != urls[url_num].bytes )
+ {
+ (void) fprintf(
+ stderr, "%s: byte count wrong\n", urls[url_num].url_str );
+ ++total_badbytes;
+ }
+ }
+ }
+ }
+
+
+static void
+progress_report( ClientData client_data, struct timeval* nowP )
+ {
+ float elapsed;
+
+ elapsed = delta_timeval( &start_at, nowP ) / 1000000.0;
+ (void) fprintf( stderr,
+ "--- %g secs, %d fetches started, %d completed, %d current\n",
+ elapsed, fetches_started, fetches_completed, num_connections );
+ }
+
+
+static void
+start_timer( ClientData client_data, struct timeval* nowP )
+ {
+ start_connection( nowP );
+ if ( do_jitter )
+ (void) tmr_create(
+ nowP, start_timer, JunkClientData,
+ (long) ( random() % range_interval ) + low_interval, 0 );
+ }
+
+
+static void
+end_timer( ClientData client_data, struct timeval* nowP )
+ {
+ finish( nowP );
+ }
+
+
+static void
+finish( struct timeval* nowP )
+ {
+ float elapsed;
+ int i;
+
+ /* Report statistics. */
+ elapsed = delta_timeval( &start_at, nowP ) / 1000000.0;
+ (void) printf(
+ "%d fetches, %d max parallel, %g bytes, in %g seconds\n",
+ fetches_completed, max_parallel, (float) total_bytes, elapsed );
+ if ( fetches_completed > 0 )
+ (void) printf(
+ "%g mean bytes/connection\n",
+ (float) total_bytes / (float) fetches_completed );
+ if ( elapsed > 0.01 )
+ {
+ (void) printf(
+ "%g fetches/sec, %g bytes/sec\n",
+ (float) fetches_completed / elapsed,
+ (float) total_bytes / elapsed );
+ }
+ if ( connects_completed > 0 )
+ (void) printf(
+ "msecs/connect: %g mean, %g max, %g min\n",
+ (float) total_connect_usecs / (float) connects_completed / 1000.0,
+ (float) max_connect_usecs / 1000.0,
+ (float) min_connect_usecs / 1000.0 );
+ if ( responses_completed > 0 )
+ (void) printf(
+ "msecs/first-response: %g mean, %g max, %g min\n",
+ (float) total_response_usecs / (float) responses_completed / 1000.0,
+ (float) max_response_usecs / 1000.0,
+ (float) min_response_usecs / 1000.0 );
+ if ( total_timeouts != 0 )
+ (void) printf( "%d timeouts\n", total_timeouts );
+ if ( do_checksum )
+ {
+ if ( total_badchecksums != 0 )
+ (void) printf( "%d bad checksums\n", total_badchecksums );
+ }
+ else
+ {
+ if ( total_badbytes != 0 )
+ (void) printf( "%d bad byte counts\n", total_badbytes );
+ }
+
+ (void) printf( "HTTP response codes:\n" );
+ for ( i = 0; i < 1000; ++i )
+ if ( http_status_counts[i] > 0 )
+ (void) printf( " code %03d -- %d\n", i, http_status_counts[i] );
+
+ tmr_destroy();
+#ifdef USE_SSL
+ if ( ssl_ctx != (SSL_CTX*) 0 )
+ SSL_CTX_free( ssl_ctx );
+#endif
+ exit( 0 );
+ }
+
+
+static long long
+delta_timeval( struct timeval* start, struct timeval* finish )
+ {
+ long long delta_secs = finish->tv_sec - start->tv_sec;
+ long long delta_usecs = finish->tv_usec - start->tv_usec;
+ return delta_secs * (long long) 1000000L + delta_usecs;
+ }
+
+
+static void*
+malloc_check( size_t size )
+ {
+ void* ptr = malloc( size );
+ check( ptr );
+ return ptr;
+ }
+
+
+static void*
+realloc_check( void* ptr, size_t size )
+ {
+ ptr = realloc( ptr, size );
+ check( ptr );
+ return ptr;
+ }
+
+
+static char*
+strdup_check( char* str )
+ {
+ str = strdup( str );
+ check( (void*) str );
+ return str;
+ }
+
+
+static void
+check( void* ptr )
+ {
+ if ( ptr == (void*) 0 )
+ {
+ (void) fprintf( stderr, "%s: out of memory\n", argv0 );
+ exit( 1 );
+ }
+ }
47 make_test_files
@@ -0,0 +1,47 @@
+#!/bin/sh
+#
+# make_test_files - create a set of files for use with http_load
+#
+# This creates a specified number of files that are either a kilobyte
+# or a megabyte in length. The files are named kNNN and mNNN.
+
+if [ $# -ne 2 ] ; then
+ echo "usage: $0 #kfiles #mfiles" >&2
+ exit 1
+fi
+
+kfiles="$1"
+mfiles="$2"
+
+tmp=mtf.$$
+ktmp=ktmp.$$
+mtmp=mtmp.$$
+rm -f $tmp $ktmp $mtmp
+
+if [ "$kfiles" -gt 0 ] ; then
+ echo "123456789012345678901234567890123456789012345678901234567890123" > $ktmp
+ cat $ktmp $ktmp $ktmp $ktmp > $tmp
+ cat $tmp $tmp $tmp $tmp > $ktmp
+ i=1
+ while [ $i -le "$kfiles" ] ; do
+ cp $ktmp "k$i"
+ i=`expr "$i" + 1`
+ done
+fi
+
+if [ "$mfiles" -gt 0 ] ; then
+ echo "123456789012345678901234567890123456789012345678901234567890123" > $mtmp
+ cat $mtmp $mtmp $mtmp $mtmp $mtmp $mtmp $mtmp $mtmp > $tmp
+ cat $tmp $tmp $tmp $tmp $tmp $tmp $tmp $tmp > $mtmp
+ cat $mtmp $mtmp $mtmp $mtmp $mtmp $mtmp $mtmp $mtmp > $tmp
+ cat $tmp $tmp $tmp $tmp $tmp $tmp $tmp $tmp > $mtmp
+ cat $mtmp $mtmp > $tmp
+ cat $tmp $tmp > $mtmp
+ i=1
+ while [ $i -le "$mfiles" ] ; do
+ cp $mtmp "m$i"
+ i=`expr "$i" + 1`
+ done
+fi
+
+rm -f $tmp $ktmp $mtmp
107 port.h
@@ -0,0 +1,107 @@
+/* port.h - portability defines */
+
+#if defined(__FreeBSD__)
+# define OS_FreeBSD
+# define ARCH "FreeBSD"
+#elif defined(__OpenBSD__)
+# define OS_OpenBSD
+# define ARCH "OpenBSD"
+#elif defined(__NetBSD__)
+# define OS_NetBSD
+# define ARCH "NetBSD"
+#elif defined(linux)
+# define OS_Linux
+# define ARCH "Linux"
+#elif defined(sun)
+# define OS_Solaris
+# define ARCH "Solaris"
+#elif defined(__osf__)
+# define OS_DigitalUnix
+# define ARCH "DigitalUnix"
+#elif defined(__svr4__)
+# define OS_SysV
+# define ARCH "SysV"
+#else
+# define OS_UNKNOWN
+# define ARCH "UNKNOWN"
+#endif
+
+#ifdef OS_FreeBSD
+# include <osreldate.h>
+# define HAVE_DAEMON
+# define HAVE_SETSID
+# define HAVE_SETLOGIN
+# define HAVE_WAITPID
+# define HAVE_HSTRERROR
+# define HAVE_TM_GMTOFF
+# define HAVE_SENDFILE
+# define HAVE_SCANDIR
+# define HAVE_INT64T
+# define HAVE_SRANDOMDEV
+# ifdef SO_ACCEPTFILTER
+# define HAVE_ACCEPT_FILTERS
+# if ( __FreeBSD_version >= 411000 )
+# define ACCEPT_FILTER_NAME "httpready"
+# else
+# define ACCEPT_FILTER_NAME "dataready"
+# endif
+# endif /* SO_ACCEPTFILTER */
+#endif /* OS_FreeBSD */
+
+#ifdef OS_OpenBSD
+# define HAVE_DAEMON
+# define HAVE_SETSID
+# define HAVE_SETLOGIN
+# define HAVE_WAITPID
+# define HAVE_HSTRERROR
+# define HAVE_TM_GMTOFF
+# define HAVE_SCANDIR
+# define HAVE_INT64T
+#endif /* OS_OpenBSD */
+
+#ifdef OS_NetBSD
+# define HAVE_DAEMON
+# define HAVE_SETSID
+# define HAVE_SETLOGIN
+# define HAVE_WAITPID
+# define HAVE_HSTRERROR
+# define HAVE_TM_GMTOFF
+# define HAVE_SCANDIR
+# define HAVE_INT64T
+#endif /* OS_NetBSD */
+
+#ifdef OS_Linux
+# define HAVE_DAEMON
+# define HAVE_SETSID
+# define HAVE_WAITPID
+# define HAVE_TM_GMTOFF
+# define HAVE_SENDFILE
+# define HAVE_LINUX_SENDFILE
+# define HAVE_SCANDIR
+# define HAVE_INT64T
+#endif /* OS_Linux */
+
+#ifdef OS_Solaris
+# define HAVE_SETSID
+# define HAVE_WAITPID
+# define HAVE_MEMORY_H
+# define HAVE_SIGSET
+# define HAVE_INT64T
+#endif /* OS_Solaris */
+
+#ifdef OS_DigitalUnix
+# define HAVE_SETSID
+# define HAVE_SETLOGIN
+# define HAVE_WAITPID
+# define HAVE_SCANDIR
+# define HAVE_TM_GMTOFF
+# define NO_SNPRINTF
+/* # define HAVE_INT64T */ /* Digital Unix 4.0d doesn't have int64_t */
+#endif /* OS_DigitalUnix */
+
+#ifdef OS_SysV
+# define HAVE_SETSID
+# define HAVE_WAITPID
+# define HAVE_MEMORY_H
+# define HAVE_SIGSET
+#endif /* OS_Solaris */
328 timers.c
@@ -0,0 +1,328 @@
+/* timers.c - simple timer routines
+**
+** Copyright © 1995,1998,2000 by Jef Poskanzer <jef@mail.acme.com>.
+** All rights reserved.
+**
+** Redistribution and use in source and binary forms, with or without
+** modification, are permitted provided that the following conditions
+** are met:
+** 1. Redistributions of source code must retain the above copyright
+** notice, this list of conditions and the following disclaimer.
+** 2. Redistributions in binary form must reproduce the above copyright
+** notice, this list of conditions and the following disclaimer in the
+** documentation and/or other materials provided with the distribution.
+**
+** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+** SUCH DAMAGE.
+*/
+
+#include <sys/types.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "timers.h"
+
+
+#define HASH_SIZE 67
+static Timer* timers[HASH_SIZE];
+static Timer* free_timers = (Timer*) 0;
+
+ClientData JunkClientData;
+
+
+
+static unsigned int
+hash( Timer* t )
+ {
+ /* We can hash on the trigger time, even though it can change over
+ ** the life of a timer via either the periodic bit or the tmr_reset()
+ ** call. This is because both of those guys call l_resort(), which
+ ** recomputes the hash and moves the timer to the appropriate list.
+ */
+ return (
+ (unsigned int) t->time.tv_sec ^
+ (unsigned int) t->time.tv_usec ) % HASH_SIZE;
+ }
+
+
+static void
+l_add( Timer* t )
+ {
+ int h = t->hash;
+ register Timer* t2;
+ register Timer* t2prev;
+
+ t2 = timers[h];
+ if ( t2 == (Timer*) 0 )
+ {
+ /* The list is empty. */
+ timers[h] = t;
+ t->prev = t->next = (Timer*) 0;
+ }
+ else
+ {
+ if ( t->time.tv_sec < t2->time.tv_sec ||
+ ( t->time.tv_sec == t2->time.tv_sec &&
+ t->time.tv_usec <= t2->time.tv_usec ) )
+ {
+ /* The new timer goes at the head of the list. */
+ timers[h] = t;
+ t->prev = (Timer*) 0;
+ t->next = t2;
+ t2->prev = t;
+ }
+ else
+ {
+ /* Walk the list to find the insertion point. */
+ for ( t2prev = t2, t2 = t2->next; t2 != (Timer*) 0;
+ t2prev = t2, t2 = t2->next )
+ {
+ if ( t->time.tv_sec < t2->time.tv_sec ||
+ ( t->time.tv_sec == t2->time.tv_sec &&
+ t->time.tv_usec <= t2->time.tv_usec ) )
+ {
+ /* Found it. */
+ t2prev->next = t;
+ t->prev = t2prev;
+ t->next = t2;
+ t2->prev = t;
+ return;
+ }
+ }
+ /* Oops, got to the end of the list. Add to tail. */
+ t2prev->next = t;
+ t->prev = t2prev;
+ t->next = (Timer*) 0;
+ }
+ }
+ }
+
+
+static void
+l_remove( Timer* t )
+ {
+ int h = t->hash;
+
+ if ( t->prev == (Timer*) 0 )
+ timers[h] = t->next;
+ else
+ t->prev->next = t->next;
+ if ( t->next != (Timer*) 0 )
+ t->next->prev = t->prev;
+ }
+
+
+static void
+l_resort( Timer* t )
+ {
+ /* Remove the timer from its old list. */
+ l_remove( t );
+ /* Recompute the hash. */
+ t->hash = hash( t );
+ /* And add it back in to its new list, sorted correctly. */
+ l_add( t );
+ }
+
+
+void
+tmr_init( void )
+ {
+ int h;
+
+ for ( h = 0; h < HASH_SIZE; ++h )
+ timers[h] = (Timer*) 0;
+ }
+
+
+Timer*
+tmr_create(
+ struct timeval* nowP, TimerProc* timer_proc, ClientData client_data,
+ long msecs, int periodic )
+ {
+ Timer* t;
+
+ if ( free_timers != (Timer*) 0 )
+ {
+ t = free_timers;
+ free_timers = t->next;
+ }
+ else
+ {
+ t = (Timer*) malloc( sizeof(Timer) );
+ if ( t == (Timer*) 0 )
+ return (Timer*) 0;
+ }
+
+ t->timer_proc = timer_proc;
+ t->client_data = client_data;
+ t->msecs = msecs;
+ t->periodic = periodic;
+ if ( nowP != (struct timeval*) 0 )
+ t->time = *nowP;
+ else
+ (void) gettimeofday( &t->time, (struct timezone*) 0 );
+ t->time.tv_sec += msecs / 1000L;
+ t->time.tv_usec += ( msecs % 1000L ) * 1000L;
+ if ( t->time.tv_usec >= 1000000L )
+ {
+ t->time.tv_sec += t->time.tv_usec / 1000000L;
+ t->time.tv_usec %= 1000000L;
+ }
+ t->hash = hash( t );
+ /* Add the new timer to the proper active list. */
+ l_add( t );
+
+ return t;
+ }
+
+
+struct timeval*
+tmr_timeout( struct timeval* nowP )
+ {
+ long msecs;
+ static struct timeval timeout;
+
+ msecs = tmr_mstimeout( nowP );
+ if ( msecs == INFTIM )
+ return (struct timeval*) 0;
+ timeout.tv_sec = msecs / 1000L;
+ timeout.tv_usec = ( msecs % 1000L ) * 1000L;
+ return &timeout;
+ }
+
+
+long
+tmr_mstimeout( struct timeval* nowP )
+ {
+ int h;
+ int gotone;
+ long msecs, m;
+ register Timer* t;
+
+ gotone = 0;
+ msecs = 0; /* make lint happy */
+ /* Since the lists are sorted, we only need to look at the
+ ** first timer on each one.
+ */
+ for ( h = 0; h < HASH_SIZE; ++h )
+ {
+ t = timers[h];
+ if ( t != (Timer*) 0 )
+ {
+ m = ( t->time.tv_sec - nowP->tv_sec ) * 1000L +
+ ( t->time.tv_usec - nowP->tv_usec ) / 1000L;
+ if ( ! gotone )
+ {
+ msecs = m;
+ gotone = 1;
+ }
+ else if ( m < msecs )
+ msecs = m;
+ }
+ }
+ if ( ! gotone )
+ return INFTIM;
+ if ( msecs <= 0 )
+ msecs = 0;
+ return msecs;
+ }
+
+
+void
+tmr_run( struct timeval* nowP )
+ {
+ int h;
+ Timer* t;
+ Timer* next;
+
+ for ( h = 0; h < HASH_SIZE; ++h )
+ for ( t = timers[h]; t != (Timer*) 0; t = next )
+ {
+ next = t->next;
+ /* Since the lists are sorted, as soon as we find a timer
+ ** that isn't ready yet, we can go on to the next list.
+ */
+ if ( t->time.tv_sec > nowP->tv_sec ||
+ ( t->time.tv_sec == nowP->tv_sec &&
+ t->time.tv_usec > nowP->tv_usec ) )
+ break;
+ (t->timer_proc)( t->client_data, nowP );
+ if ( t->periodic )
+ {
+ /* Reschedule. */
+ t->time.tv_sec += t->msecs / 1000L;
+ t->time.tv_usec += ( t->msecs % 1000L ) * 1000L;
+ if ( t->time.tv_usec >= 1000000L )
+ {
+ t->time.tv_sec += t->time.tv_usec / 1000000L;
+ t->time.tv_usec %= 1000000L;
+ }
+ l_resort( t );
+ }
+ else
+ tmr_cancel( t );
+ }
+ }
+
+
+void
+tmr_reset( struct timeval* nowP, Timer* t )
+ {
+ t->time = *nowP;
+ t->time.tv_sec += t->msecs / 1000L;
+ t->time.tv_usec += ( t->msecs % 1000L ) * 1000L;
+ if ( t->time.tv_usec >= 1000000L )
+ {