/* $Id: maxhttp_cps.C,v 1.4 2000/08/29 06:43:58 fubob Exp $ */

/* Use this program to simulate a herd of HTTP clients.  Each
   client connects, asks for "/", reads the page, then closes.  You
   can test the maximum number of sustainable connections per
   second to a web server.  Warning. this is almost as fast
   as a plain TCP connections.  Running longer than a few seconds
   will likely use up resources.

*/

/*
 *
 * Copyright (C) 2000 Kevin Fu (fubob@mit.edu)
 *
 * 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, 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 "maxhttp_cps.h"
#include "parseopt.h"

unsigned long count = 0;
unsigned long attempted = 0;
u_int64_t bytecount = 0;

u_int64_t starttime;
char *opt_name = NULL;
bool opt_verbose = false;
int opt_test = 0;
int opt_port = 8080;
in_addr addr;

static char request1[70] = "GET /symlink.txt HTTP/1.0\r\n\r\n";
int responselen1 = 335;
int requestlen1 = strlen (request1);

static char request2[70] = "GET /one/two/three/symlink.txt HTTP/1.0\r\n\r\n";
int responselen2 = 335;
int requestlen2 = strlen (request2);

char bitbucket[2048];

inline u_int64_t
gettime ()
{
  timeval tv;
  gettimeofday (&tv, NULL);
  return tv.tv_sec * INT64(1000000) + tv.tv_usec;
}


void fail (int err)
{
  warn << "connection failed" << strerror (errno) << "\n";
}


void init ()
{
  tcpconnect (addr, opt_port, wrap (getsockres));
}

void getsockres (int fd)
{
  attempted++;
  if (fd < 0) {
    timecb (timenow +1, wrap (init));
  } else {
    tcp_nodelay (fd);
    int wrlen = write (fd, 
		       ((opt_test == 1)?request1:request2),
		       ((opt_test == 1)?requestlen1:requestlen2));
  
    if (wrlen != ((opt_test == 1)?requestlen1:requestlen2)) {
      warn << "Fuck!\n";
      close (fd);
    } else {

      shutdown (fd, SHUT_WR);
      fdcb (fd, selread, wrap (resp, fd));

    }
  }
}

void resp (int fd)
{
  int rdlen = read (fd, bitbucket, 
		    ((opt_test == 1)?responselen1:responselen2));

  if (rdlen < 0 && errno != EAGAIN) {
    fdcb (fd, selread, NULL);
    fail(errno);
  } else if (rdlen == ((opt_test == 1)?responselen1:responselen2)) {
    fdcb (fd, selread, NULL);
    
    count++;
    close (fd);
    init ();

  } else  {
    warnx << "Didn't get a proper response. rdlen: " << rdlen << "\n";
    close (fd);
    init ();
  
  }  

}


void
finish ()
{
  u_int64_t elapsed_usec = gettime () - starttime;
  if (opt_verbose) {
    printf ("Bytes: %lu\n",
	    ((opt_test == 1)?responselen1:responselen2) * count);
    printf (" usec: %qd\n", elapsed_usec);
    printf (" MB/s: %f\n", 
	    0.953674316406 * ((opt_test == 1)?responselen1:responselen2)
	    * count /elapsed_usec);
    printf ("Successful plays: %lu\n", count);
    printf ("Attempted plays: %lu\n", attempted);
    printf ("Successful plays/s: %f\n", 
	    count / (elapsed_usec / 1e6F));
    printf ("Attempted plays/s: %f\n\n", 
	    attempted / (elapsed_usec/ 1e6F));

  } else {

    printf ("%5s %10lu %10qd %10f %6lu %6lu %8.2f %8.2f\n",
	    opt_name?opt_name:"Local",
	    ((opt_test == 1)?responselen1:responselen2) * count, 
	    elapsed_usec,
	    0.953674316406 * 
	    ((opt_test == 1)?responselen1:responselen2) * count / elapsed_usec,
	    count,
	    attempted,
	    count / (elapsed_usec / 1e6F),
	    attempted / (elapsed_usec/ 1e6F));
  }

  exit (0);
}

void
usage ()
{
  warnx << " Test 1 == Single component\n"
	<< " Test 2 == Multi component\n\n";

  fatal << "\nusage: " << progname << " [-p trigger-port] [-n num-clients]\n"
	<< "         [-t seconds] [-l label] [-v] -r test-num server port\n\n";

}

int
main (int argc, char **argv)
{
  setprogname (argv[0]);

  int opt_triggerport = 0;
  int opt_nclients = 20;
  double opt_nseconds = 5.0;

  int ch;
  while ((ch = getopt (argc, argv, "p:n:t:r:l:r:v")) != -1)
    switch (ch) {
    case 'p':
      if (!convertint (optarg, &opt_triggerport))
	usage ();
      break;
    case 'n':
      if (!convertint (optarg, &opt_nclients))
	usage ();
      break;
    case 't':
      opt_nseconds = atof (optarg);
      if (opt_nseconds <= 0)
	usage ();
      break;
    case 'v':
      opt_verbose = true;
      break;
    case 'l':
      opt_name = optarg;
      break;
    case 'r':
      if (!convertint (optarg, &opt_test))
	usage ();
      break;
    default:
      usage ();
      break;
    }

  if ( (optind + 2 != argc)
       || ((opt_test != 1) && (opt_test != 2)))
    usage ();

  if (!convertint (argv[optind+1], &opt_port))
    usage ();

  hostent *hp = gethostbyname (argv[optind]);
  if (!hp)
    fatal << argv[1] << ": no such host\n";
  addr = *(in_addr *) hp->h_addr;

  itimerval itv;
  bzero (&itv, sizeof (itv));
  itv.it_value.tv_sec = long (opt_nseconds);
  itv.it_value.tv_usec = long (1000000 * (opt_nseconds - itv.it_value.tv_sec));
  sigcb (SIGALRM, wrap (finish));


  if (opt_verbose) {
    printf ("Clients     Est. Duration (usec)    Bytes   Act. Duration (usec)   MB/s   Succ. Plays   Attem. Plays  Succ. Plays/s   Attem. Plays/s\n");
 
    printf ("[ %d clients / %f seconds ]\n", opt_nclients, opt_nseconds);
  }

  if (opt_triggerport) {
    int fd = inetsocket (SOCK_DGRAM, opt_triggerport);
    if (fd < 0)
      fatal ("socket: %m\n");
    char c;
    socklen_t l = 0;
    recvfrom (fd, &c, 1, 0, NULL, &l);
  }

  if (setitimer (ITIMER_REAL, &itv, NULL) < 0)
    fatal ("setitimer: %m\n");

  starttime = gettime ();

  for (int i=0; i< opt_nclients; i++) 
    init();

  amain ();
}


