[httperf] [PATCH] Run multiple clients from one machine

Ian Wienand ianw@gelato.unsw.edu.au
Tue, 21 Oct 2003 17:11:32 +1000


--4Ckj6UjgE2iN1+kY
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

Hi,

I've been using httperf and wanted a way to start a number of clients
from a single machine.

I added a program called 'httperfctl' and two flags to httperf
'--listen' and '--daemon'.  

--listen puts httperf in 'listen' mode, where httperfctl can connect
to it and send it the argv to execute, basically.  --daemon makes it
leave the terminal.

httperfctl takes clients with -c and anything after a '--' get passed
as arguments to the clients.  The clients then pipe all their output
back to httperfctl which writes them out to log files, for which there
are some options to name them etc. 

It shouldn't let two jobs run at once, and if the server is quit with
ctrl-c it should try to quit all client instances running.

This has been working well for me over the past few days with a few
machines.  I hope it can be useful to someone else too!

Thanks,

-i
ianw@gelato.unsw.edu.au
http://www.gelato.unsw.edu.au

--4Ckj6UjgE2iN1+kY
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="httperfctl.diff"

Index: Makefile.in
===================================================================
RCS file: /home/ianw/cvs/httperf/Makefile.in,v
retrieving revision 1.1.1.1
retrieving revision 1.5
diff -u -r1.1.1.1 -r1.5
--- Makefile.in	14 Oct 2003 04:38:52 -0000	1.1.1.1
+++ Makefile.in	16 Oct 2003 22:46:04 -0000	1.5
@@ -35,13 +35,16 @@
 
 .c.o:
 	$(COMPILE) $<
+HTTPERFCTL_OBJS = httperfctl.o
 
 HTTPERF_OBJS = httperf.o object.o call.o conn.o sess.o core.o event.o \
 	http.o timer.o
 
 TTEST_OBJS = ttest.o timer.o
 
-all: all-recursive httperf idleconn
+all: all-recursive httperf idleconn httperfctl
+
+httperfctl: $(HTTPERFCTL_OBJS)
 
 httperf: $(HTTPERF_OBJS) gen/libgen.a stat/libstat.a lib/libutil.a
 	$(LINK) $(HTTPERF_OBJS) \
@@ -53,15 +56,16 @@
 install: install-recursive httperf
 	$(MKDIR) $(bindir) $(mandir)/man1
 	$(INSTALL_PROGRAM) httperf $(bindir)/httperf
+	$(INSTALL_PROGRAM) httperfctl $(bindir)/httperfctl
 	$(INSTALL_DATA) $(srcdir)/httperf.man $(mandir)/man1/httperf.1
 
 ttest: ttest.o timer.o
 
 clean:	clean-recursive
-	rm -f $(HTTPERF_OBJS) $(TTEST_OBJS) idleconn.o httperf idleconn ttest
+	rm -f $(HTTPERF_OBJS) $(TTEST_OBJS) $(HTTPERFCTL_OBJS) idleconn.o httperf idleconn ttest httperfctl
 
 distclean: distclean-recursive
-	rm -f *~
+	rm -f *~ config.cache config.h config.log config.status Makefile
 
 all-recursive install-recursive clean-recursive distclean-recursive \
 	depend-recursive:
Index: README
===================================================================
RCS file: /home/ianw/cvs/httperf/README,v
retrieving revision 1.1.1.1
retrieving revision 1.3
diff -u -r1.1.1.1 -r1.3
--- README	14 Oct 2003 04:38:52 -0000	1.1.1.1
+++ README	16 Oct 2003 03:37:47 -0000	1.3
@@ -401,3 +401,23 @@
 initial retransmission timeout is on the order of 3 seconds).  This is
 usually OK, except that one should keep in mind that it has the effect
 of truncating the connection life time distribution.
+
+** Running multiple client connections
+
+You can use the httperfctl program in conjunction with a number of
+httperf instances started with the --listen command.  You may
+additionally specify --daemon with --listen to make httperf fork into
+the background.
+
+httperfctl will attempt to contact the clients you pass with the -c
+option, and output their results to a series of files (you can choose
+where to output them and any particular prefix).  Any options passed
+in after the standard option separator '--' will be passed directly to
+the clients as arguments to httperf.
+
+Note that the clients will only allow one httperf test to be running
+at any particular time.  httperfctl is fairly rudimentary;
+enhancements might include making it automatically average out results
+and using separate command/data channels to the client.
+
+See httperfctl -h for information on the flags.
Index: httperf.c
===================================================================
RCS file: /home/ianw/cvs/httperf/httperf.c,v
retrieving revision 1.1.1.1
retrieving revision 1.17
diff -u -r1.1.1.1 -r1.17
--- httperf.c	14 Oct 2003 04:38:52 -0000	1.1.1.1
+++ httperf.c	21 Oct 2003 04:27:58 -0000	1.17
@@ -61,6 +61,13 @@
 
 #include <sys/time.h>
 #include <sys/resource.h>
+#include <sys/wait.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <netdb.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
 
 #include <core.h>
 #include <event.h>
@@ -72,6 +79,9 @@
 #  include <openssl/rand.h>
 #endif
 
+/* function that listens for server commands */
+void listen_mode(int argc, char *argv[]);
+
 #define RATE_INTERVAL	5.0
 
 const char *prog_name;
@@ -106,6 +116,8 @@
     {"http-version", required_argument, (int *) &param.http_version,	0},
     {"max-connections", required_argument, &param.max_conns,		0},
     {"max-piped-calls", required_argument, &param.max_piped,		0},
+    {"listen", 	     optional_argument, (int *) &param.listen, 		0},
+    {"daemon", 	     no_argument,	&param.daemon_mode,		1},
     {"method",	     required_argument,	(int *) &param.method,		0},
     {"no-host-hdr",  no_argument,	&param.no_host_hdr,		1},
     {"num-calls",    required_argument, (int *) &param.num_calls,	0},
@@ -159,7 +171,8 @@
 	  "\t[--think-timeout X] [--timeout X] [--uri S] [--verbose] "
 	  "[--version]\n"
 	  "\t[--wlog y|n,file] [--wsess N,N,X] [--wsesslog N,X,file]\n"
-	  "\t[--wset N,X]\n",
+	  "\t[--wset N,X]\n"
+	  "\t[--listen N] [--daemon]\n",
 	  prog_name);
 }
 
@@ -263,7 +276,25 @@
 	case 0:
 	  flag = longopts[longindex].flag;
 
-	  if (flag == &param.method)
+	  if (flag == &param.listen) 
+	    {
+	      if (!optarg) 
+		param.listen = 6123;
+	      else 
+		{
+		  errno = 0;
+		  param.listen = strtoul (optarg, &end, 10);
+		  if (errno == ERANGE || end == optarg || *end
+		      || (unsigned) param.listen > 0xffff)
+		    {
+		      fprintf (stderr, "%s: illegal listen port number %s\n",
+			       prog_name, optarg);
+		      exit (1);
+		    }
+		}
+	      param.listen_mode = 1;
+	    }
+  	  else if (flag == &param.method)
 	    param.method = optarg;
 	  else if (flag == &param.additional_header)
 	    param.additional_header = optarg;
@@ -786,6 +817,10 @@
 	}
     }
 
+  if (param.listen_mode)
+	  listen_mode(argc, argv);
+          /* processing does not continue */
+
 #ifdef HAVE_SSL
   if (param.use_ssl)
     {
@@ -971,3 +1006,247 @@
 
   return 0;
 }
+
+/*
+ * Support for listen/daemon mode below 
+ */
+
+int already_running;
+
+/* 
+ * When our child exits, we are OK to
+ * accept more requests
+ */
+void sigchld_handler(int s) 
+{
+  while(wait(NULL) > 0);
+  already_running = 0;
+}
+
+/*
+ * Unmarshall a string of args sent to us by the server.
+ * argv[]       : pointer to the argv the client should get
+ * argc         : pointer to the argc the client should get
+ * in_buf       : the start message sent from the server
+ * client_path  : path to httperf (for argv[0])
+ */
+int unmarshall_args(char **argv[], int *argc, char *in_buf, char *client_argv[])
+{
+  char **argv_buf;
+  char *argptr;
+  int i;
+
+  if (sscanf(in_buf, "START %d", argc) != 1)
+    {
+      fprintf(stderr,"Invalid protocol initalisation!");
+      return -1;
+    }
+
+  /* argc +2, 1 for argv0, 1 for null term*/
+  if ((argv_buf = malloc((*argc+2)*sizeof(char*))) == NULL)
+      panic("Out of buffer space\n");
+  if ((argv_buf[0] = malloc(strlen(client_argv[0])+1)) == NULL)
+      panic("Out of buffer space\n");
+  strcpy(argv_buf[0], client_argv[0]);
+
+  /* we just have a long list of contiguous strings */
+  argptr = &in_buf[strlen(in_buf) + 1];
+  for(i = 1; i < *argc + 1 ; i++ ) 
+    {
+      if((argv_buf[i] = malloc(strlen(argptr)+1)) == NULL)
+	panic("Out of buffer space\n");
+      strcpy(argv_buf[i], argptr);      
+      argptr += strlen(argptr)+1;
+    }
+  /* null terminate */
+  argv_buf[i+1] = '\0';
+
+  *argv = argv_buf;
+ 
+  return 0;
+}
+
+
+void listen_mode(int argc, char *argv[]) 
+{
+
+  int sockfd, new_fd;
+  struct sockaddr_in my_addr;
+  struct sockaddr_in control_addr;
+  int sin_size;
+  int nbytes;
+  int pid;
+  
+  char buf[BUFSIZ];
+  
+  struct sigaction sa;
+  
+  int yes=1;
+  int port;
+
+  char **client_argv;
+
+  if ( param.listen == 0 )
+    port = 6123;
+  else
+    port = param.listen;
+
+  if ( param.daemon_mode == 1 ) {
+	  pid = fork ();
+	  switch (pid)
+	  {
+	  case -1:
+		  perror ("Fork failed");
+		  exit (1);
+	  case 0:
+		  break;
+	  default:
+		  exit (0);
+	  }
+	  setsid ();
+	  pid = fork ();
+	  switch (pid)
+	  {
+	  case -1:
+		  perror ("Fork failed");
+		  exit (1);
+	  case 0:
+		  break;
+	  default:
+		  exit (0);
+	  }
+	  
+  }
+  
+  if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) 
+    {
+      perror("Error creating socket");
+      exit(1);
+    }
+  
+  if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) 
+    {
+      perror("Error setting socket options");
+      exit(1);
+    }
+
+  my_addr.sin_family = AF_INET;
+  my_addr.sin_port = htons(port);
+  my_addr.sin_addr.s_addr = INADDR_ANY;
+  memset(&(my_addr.sin_zero), '\0', 8);
+
+  if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1 ) 
+    {
+      perror("Error binding to socket");
+      exit(1);
+    }
+
+  if (listen(sockfd, 1) == -1 ) 
+    {
+      perror("Error Listening");
+      exit(1);
+    }
+
+  sa.sa_handler = sigchld_handler;
+  sigemptyset(&sa.sa_mask);
+  sa.sa_flags = SA_RESTART;
+  if (sigaction(SIGCHLD, &sa, NULL) == -1) 
+    {
+      perror("Error registering SIGCHLD handler");
+      exit(1);
+    }
+
+  while (1) {
+    
+    sin_size = sizeof(struct sockaddr_in);
+    if ((new_fd = accept(sockfd, (struct sockaddr *)&control_addr, &sin_size)) == -1 ) 
+      {
+	perror("Error in accept");
+	exit(1);
+      }
+
+    /* Do not accept more than one job at a time */
+    if ( already_running ) 
+      {
+	send(new_fd, "ALREADY\n", 8, 0);
+	close(new_fd);
+	continue;
+      }
+    else
+      already_running = 1;
+
+    switch(fork()) 
+      {
+      case 0:
+	/* child : close the listening socket and exec the httperf program */
+	close(sockfd);
+	nbytes = recv(new_fd, buf, BUFSIZ, 0);
+	if ( nbytes == -1 ) {
+	  perror("Error receiving");
+	  exit(1);
+	}
+
+	switch(pid = fork())
+	  {
+	  case 0:
+	    /* Child : unmarshall the arguments and exec the httperf command */
+	    if (unmarshall_args(&client_argv, &argc, buf, argv) == -1)
+	      {
+		fprintf(stderr,"%s Protocol error\n", prog_name);
+		exit(1);
+	      }
+	    dup2(new_fd, 1);
+	    dup2(new_fd, 2);
+	    execvp(client_argv[0], client_argv);
+	    fprintf(stderr,"Can not find httperf -- is it in your path?\n");
+	    exit(1);
+	  case -1:
+	    perror("Forking child to exec failed");
+	    exit(1);
+	  default:
+	    /* Parent : wait for QUIT to come down the socket, if it does, 
+	     *          kill the child
+	     */
+	    while( !waitpid(-1, NULL, WNOHANG) ) 
+	      {
+		if((nbytes=recv(new_fd, buf, BUFSIZ, MSG_DONTWAIT)) == -1)
+		  {
+		    if (errno == EWOULDBLOCK )
+		      {
+			sleep(2);
+			continue;
+		      }
+		    else
+		      exit(1);
+		  }
+		else 
+		  if (!strncmp(buf, "QUIT", 4))
+		    {
+		      kill(pid, SIGKILL);
+		      exit(1);
+		    }
+	      }
+	  }
+	
+	/*
+	 * When we get here, httperf finished successfully or was quit. 
+	 * When we exit below, the parent will catch SIGCHLD and 
+	 * reset the already_running flag.  Pad "QUIT" with newlines.
+	 */
+	send(new_fd, "\nQUIT\n", 6, 0);
+	exit(0);
+	break;
+      case -1:
+	perror("fork");
+	exit(1);
+      default:
+	/*  Parent : go back and listen again, rejecting any further 
+	 *  requests until child is complete.
+	 */
+	break;
+      }
+
+  }
+
+}
+
Index: httperf.h
===================================================================
RCS file: /home/ianw/cvs/httperf/httperf.h,v
retrieving revision 1.1.1.1
retrieving revision 1.5
diff -u -r1.1.1.1 -r1.5
--- httperf.h	14 Oct 2003 04:38:52 -0000	1.1.1.1
+++ httperf.h	15 Oct 2003 05:20:04 -0000	1.5
@@ -128,6 +128,10 @@
 #endif
     const char *additional_header;	/* additional request header(s) */
     const char *method;	/* default call method */
+    int listen_mode;	/* are we listening for a control client? */
+    int listen;		/* port to listen on */
+    int daemon_mode;    /* fork into the background and wait for commands? */
+    int short_results;  /* print out short results suitable for httperfctl to average? */
     struct
       {
 	u_int id;
Index: httperf.man
===================================================================
RCS file: /home/ianw/cvs/httperf/httperf.man,v
retrieving revision 1.1.1.1
retrieving revision 1.3
diff -u -r1.1.1.1 -r1.3
--- httperf.man	14 Oct 2003 04:38:52 -0000	1.1.1.1
+++ httperf.man	21 Oct 2003 06:56:32 -0000	1.3
@@ -66,6 +66,10 @@
 .IR N , X , F ]
 .RB [ --wset
 .IR N , X ]
+.RB [ --listen 
+.IR N
+.RB ]
+.RB [ --daemon ]
 .SH DESCRIPTION
 .B httperf
 is a tool to measure web server performance.  It speaks the HTTP
@@ -780,6 +784,18 @@
 .BR /wset1024/0/1/0/3.html .
 In other words, the files on the server need to be organized as a
 10ary tree.
+.TP
+.BI --listen= N
+Listen for commands from the
+.IR httperfctl
+program.  Optionally pass 
+.IR N
+as the port number to listen on (default 6123).
+.TP
+.BI --daemon
+When specified with
+.B --listen
+results in httperf forking into the background.
 .SH OUTPUT
 This section describes the statistics output at the end of each test
 run.  The basic information shown below is printed independent of the
Index: httperfctl.c
===================================================================
RCS file: httperfctl.c
diff -N httperfctl.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ httperfctl.c	21 Oct 2003 07:06:45 -0000	1.18
@@ -0,0 +1,448 @@
+/*
+    httperfctl -- a tool for controlling httperf on remote hosts 
+    Copyright (C) 2003 Gelato@UNSW 
+    Contributed by Ian Wienand <ianw@gelato.unsw.edu.au>
+
+    This file is part of httperf, a web server performance measurment
+    tool.
+
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+    02111-1307 USA
+*/
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include <signal.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <getopt.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#define MAX_HOSTS 100
+
+/* default port */
+int port = 6123;
+
+int log_to_stdout;
+char *output_dir = "./";
+char *run_name;
+int be_verbose;
+
+struct hosts_t
+{
+  int fd;
+  FILE *logfd;
+  char *hostname;
+} hosts[MAX_HOSTS];
+
+int nhosts;
+
+void
+usage (void)
+{
+  printf
+    ("Usage: httperfctl [-p port] [-o dir] [-s] [-r run_name] [-c client1] [-c client2] --  client args\n"
+     "Control a number of httperf commands on remote clients.\n"
+     "Arguments\n"
+     "\t -p port     : communicate on specified port (default 6123).\n"
+     "\t -o dir      : put output of client's httperf command in a file in dir.\n"
+     "\t -s          : output all clients output to stdout.\n"
+     "\t               Default is current directory.\n"
+     "\t -r run_name : prepend output filename with string run_name.\n"
+     "\t -c client   : client to connect to.  Use multiple -c commands for \n"
+     "\t               multiple clients.  The client(s) must have been passed the\n"
+     "\t               --listen option to be ready to receive the request.\n"
+     "\t -v          : be more verbose with output.\n"
+     "\t -h          : print this message.\n");
+  exit (0);
+}
+
+void
+verbose_printf (const char *msg, ...)
+{
+  va_list va;
+
+  if (be_verbose)
+    {
+      va_start (va, msg);
+      vfprintf (stdout, msg, va);
+      va_end (va);
+    }
+}
+
+
+/*
+ * Connect to a host and add it to the host array, setup output log
+ * file, return the fd to listen to.
+ */
+int
+add_host (char *hostname)
+{
+  struct hostent *he;
+  struct sockaddr_in host;
+
+  verbose_printf ("Add host %s\n", hostname);
+
+  if ((he = gethostbyname (hostname)) == NULL)
+    {
+      printf ("httperfctl: %s %s\n", hstrerror (h_errno), hostname);
+      exit (1);
+    }
+
+  if ((hosts[nhosts].fd = socket (AF_INET, SOCK_STREAM, 0)) == -1)
+    {
+      perror ("socket");
+      exit (1);
+    }
+
+  host.sin_family = AF_INET;
+  host.sin_port = htons (port);
+  host.sin_addr = *((struct in_addr *) he->h_addr);
+  memset (&(host.sin_zero), '\0', 8);
+
+  if (connect
+      (hosts[nhosts].fd, (struct sockaddr *) &host,
+       sizeof (struct sockaddr)) == -1)
+    {
+      fprintf (stderr,
+	       "httperfctl: %s : Is the server listening?  Is it already processing a request?\n",
+	       hstrerror (h_errno));
+      exit (1);
+    }
+
+  if ((hosts[nhosts].hostname = malloc (strlen (he->h_name))) == NULL)
+    {
+      fprintf (stderr, "Out of buffer space\n");
+      exit (1);
+    }
+  strcpy (hosts[nhosts].hostname, he->h_name);
+
+  if (log_to_stdout)
+    hosts[nhosts].logfd = stdout;
+  else
+    {
+      char filename[BUFSIZ];
+      int output_num = 0;
+
+      for (output_num = 0; output_num < 1000; output_num++)
+	{
+	  if (run_name)
+	    sprintf (filename, "%s/%s-%s.%d.httperf", output_dir, run_name,
+		     hostname, output_num);
+	  else
+	    sprintf (filename, "%s/%s.%d.httperf", output_dir, hostname,
+		     output_num);
+
+	  if ((hosts[nhosts].logfd = fopen (filename, "r")) != NULL)
+	    {
+	      fclose (hosts[nhosts].logfd);
+	      continue;
+	    }
+
+	  verbose_printf ("Host %s logging to %s\n", hostname, filename);
+
+	  hosts[nhosts].logfd = fopen (filename, "w");
+	  if (hosts[nhosts].logfd == NULL)
+	    {
+	      perror ("open log file");
+	      exit (1);
+	    }
+	  break;
+	}
+
+    }
+  return (hosts[nhosts].fd);
+
+}
+
+/* 
+ * See if all our clients have reported in 
+ */
+int
+any_outstanding (fd_set hostfds)
+{
+  int i;
+  for (i = 0; i < nhosts; i++)
+    if (FD_ISSET (hosts[i].fd, &hostfds))
+      return 1;
+  return 0;
+}
+
+
+/* 
+ * Each client listens for 'QUIT' whilst httperf is 
+ * running; this will abort the run
+ */
+void
+sigint_handler (int sig)
+{
+  int i;
+  struct stat isafd;
+
+  verbose_printf ("Caught SIGINT, trying to quit clients\n");
+
+  for (i = 0; i < nhosts; i++)
+    {
+      fstat (hosts[i].fd, &isafd);
+      if (S_ISSOCK (isafd.st_mode))
+	{
+	  send (hosts[i].fd, "QUIT", 4, 0);
+	  close (hosts[i].fd);
+	}
+    }
+
+  verbose_printf ("Exit\n");
+
+  exit (0);
+}
+
+
+/* 
+ * Organise arguments into a string suitable for sending
+ * across the network.  Just concatentate all the strings
+ * together, and put a header that says how many strings
+ * there are.
+ * client_argv   :should be a char array big enough to hold
+ *                 all of argv as one string.
+ * argv_buf      :large buffer for argv string
+ * argv_buf_size :size of argv_buf
+ * start_at      :argc to start parsing at
+ * argc          :argc from main()
+ * argv          :argv from main()
+ * returns       :length to send across the network
+ */
+int
+marshall_args (char *argv_buf, int argv_buf_size, int start_at, int argc,
+	       char *argv[])
+{
+  int argv_buf_len = 0;
+  int i;
+
+  argv_buf_len = sprintf (argv_buf, "START %d", argc - start_at) + 1;
+
+  for (i = start_at; i < argc; i++)
+    {
+      if (argv_buf_len > argv_buf_size)
+	{
+	  fprintf (stderr, "Too many arguments\n");
+	  exit (1);
+	}
+      argv_buf_len += sprintf (&argv_buf[argv_buf_len], "%s", argv[i]) + 1;
+    }
+
+  return argv_buf_len;
+}
+
+
+int
+main (int argc, char *argv[])
+{
+
+  int nbytes;
+  char argv_buf[BUFSIZ], buf[BUFSIZ];
+  int i;
+
+  struct sigaction sa;
+
+  extern char *optarg;
+  extern int optind, optopt;
+  int errflg = 0, c;
+
+  char *hosts_to_add[MAX_HOSTS];
+  int num_hosts_to_add = 0;
+
+  fd_set hostfds, fds;
+  int fd, max = 0;
+  int host_args_len;
+
+  FD_ZERO (&hostfds);
+
+  while ((c = getopt (argc, argv, "vp:sr:o:c:h")) != -1)
+    {
+      switch (c)
+	{
+	case 'v':
+	  be_verbose = 1;
+	  break;
+	case 's':
+	  log_to_stdout = 1;
+	  break;
+	case 'o':
+	  output_dir = optarg;
+	  break;
+	case 'r':
+	  run_name = optarg;
+	  break;
+	case 'p':
+	  port = atoi (optarg);
+	  if (errno == ERANGE || (unsigned) port > 0xffff)
+	    {
+	      fprintf (stderr, "httperfctl: illegal listen port number %s\n",
+		       optarg);
+	      exit (1);
+	    }
+	  break;
+	case 'c':
+	  /* 
+	   * Delay processing so other options are valid
+	   * if they are passed in *after* a -c
+	   */
+	  hosts_to_add[num_hosts_to_add++] = optarg;
+	  break;
+	case 'h':
+	  usage ();
+	case ':':
+	  errflg++;
+	  break;
+	case '?':
+	  errflg++;
+	}
+    }
+
+  if (errflg)
+    {
+      fprintf (stderr, "Try -h for help\n");
+      exit (2);
+    }
+
+  if (num_hosts_to_add == 0)
+    {
+      fprintf (stderr,
+	       "You must specify some clients with -c, try -h for help\n");
+      exit (1);
+    }
+
+  /* process hosts */
+  for (i = 0; i < num_hosts_to_add; i++)
+    {
+      fd = add_host (hosts_to_add[i]);
+      FD_SET (fd, &hostfds);
+      if (max < fd + 1)
+	max = fd + 1;
+      nhosts++;
+    }
+
+  /* marshall remaining aguments into a string for sending */
+  host_args_len = marshall_args (argv_buf, BUFSIZ, optind, argc, argv);
+
+  /* if we quit we should try to make the clients quit */
+  sa.sa_handler = sigint_handler;
+  sigemptyset (&sa.sa_mask);
+  sa.sa_flags = SA_RESTART;
+  if (sigaction (SIGINT, &sa, NULL) == -1)
+    {
+      perror ("sigaction");
+      exit (1);
+    }
+
+  for (i = 0; i < nhosts; i++)
+    {
+      verbose_printf ("Requesting host %s start\n", hosts[i].hostname);
+      send (hosts[i].fd, argv_buf, host_args_len, 0);
+    }
+
+  while (1)
+    {
+      fds = hostfds;
+
+      switch (select (max, &fds, NULL, NULL, NULL))
+	{
+	case -1:
+	  perror ("select");
+	  exit (1);
+	case 0:
+	  break;
+	}
+
+      /* 
+       * we use a fairly dodgey little protocol for the momemnt.  send
+       * START "args" to start the client; if it replies "ALREADY" it
+       * means it's already doing a run, so we fail. Otherwise it
+       * pipes it's stdout back to us, and gives us QUIT on a newline
+       * when it's done.
+       *
+       * obviously it would be better to have a separate
+       * results/control channel etc ...
+       */
+
+      for (i = 0; i < nhosts; i++)
+	{
+	  if (FD_ISSET (hosts[i].fd, &fds))
+	    {
+	      while ((nbytes = recv (hosts[i].fd, buf, BUFSIZ, 0)) > 0)
+		{
+		  char linebuf[BUFSIZ];
+		  int j = 0, lastline = 0;
+
+		  buf[nbytes + 1] = '\0';
+
+		  /* split up the input and parse with new line 
+		   * deliminators.  Look for 'ALREADY' or 'QUIT'
+		   * on lines; otherwise it's program output
+		   */
+		  for (; j < nbytes; j++)
+		    if (buf[j] == '\n')
+		      {
+			int linelen = j - lastline + 1;
+			strncpy (linebuf, &buf[lastline], linelen);
+			/* new line starts after this \n */
+			lastline = j + 1;
+			/* null terminate previous line */
+			linebuf[linelen] = '\0';
+
+			if (strstr (linebuf, "ALREADY") != NULL)
+			  {
+			    fprintf (stderr,
+				     "httperfctl: client %s reports it is already running, can't continue.\n",
+				     hosts[i].hostname);
+			    exit (1);
+			  }
+			else if (strstr (linebuf, "QUIT"))
+			  {
+			    verbose_printf
+			      ("Host %s reports it is finished\n",
+			       hosts[i].hostname);
+			    FD_CLR (hosts[i].fd, &hostfds);
+			    close (hosts[i].fd);
+			  }
+			else
+			  fprintf (hosts[i].logfd, "%s", linebuf);
+		      }
+		}
+	    }
+	}
+
+      /* check if we are still watching anything, if not we can quit. */
+      if (any_outstanding (hostfds))
+	continue;
+      else
+	break;
+    }
+
+  verbose_printf ("Exit\n");
+
+  exit (0);
+
+}
Index: gen/Makefile.in
===================================================================
RCS file: /home/ianw/cvs/httperf/gen/Makefile.in,v
retrieving revision 1.1.1.1
retrieving revision 1.2
diff -u -r1.1.1.1 -r1.2
--- gen/Makefile.in	14 Oct 2003 04:38:53 -0000	1.1.1.1
+++ gen/Makefile.in	16 Oct 2003 05:31:31 -0000	1.2
@@ -47,6 +47,6 @@
 	rm -f libgen.a $(LIBGEN_OBJS)
 
 distclean: clean
-	rm -f *~
+	rm -f *~ Makefile
 
 .PHONY: all install clean distclean depend
Index: lib/Makefile.in
===================================================================
RCS file: /home/ianw/cvs/httperf/lib/Makefile.in,v
retrieving revision 1.1.1.1
retrieving revision 1.2
diff -u -r1.1.1.1 -r1.2
--- lib/Makefile.in	14 Oct 2003 04:38:53 -0000	1.1.1.1
+++ lib/Makefile.in	16 Oct 2003 05:31:31 -0000	1.2
@@ -43,6 +43,6 @@
 	rm -f libutil.a $(LIBUTIL_OBJS)
 
 distclean: clean
-	rm -f *~
+	rm -f *~ Makefile
 
 .PHONY: all install clean distclean depend
Index: stat/Makefile.in
===================================================================
RCS file: /home/ianw/cvs/httperf/stat/Makefile.in,v
retrieving revision 1.1.1.1
retrieving revision 1.2
diff -u -r1.1.1.1 -r1.2
--- stat/Makefile.in	14 Oct 2003 04:38:53 -0000	1.1.1.1
+++ stat/Makefile.in	16 Oct 2003 05:31:31 -0000	1.2
@@ -44,6 +44,6 @@
 	rm -f libstat.a $(LIBSTAT_OBJS)
 
 distclean: clean
-	rm -f *~
+	rm -f *~ Makefile
 
 .PHONY: all install clean distclean depend

--4Ckj6UjgE2iN1+kY--