/*
    SWBench - A Simple Webserver Benchmark program
                   (last modified 29-Mar-1998)

    Copyright (C) 1998  Lars Hoss (Lars.Hoss@munich.netsurf.de)

    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, and provided that the above
    copyright and permission notice is included with all distributed
    copies of this or derived software.

    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


    Credits:
    
    - 24-Mar-1998 -mat- filid brandy: Insertion of replay-code

    - 16-Jun-2002 -jpl- slight corrections
*/


/*********************************************/
/* STANDARD INCLUDES                         */
/*********************************************/

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/signal.h>

/*********************************************/
/* DEFINITINOS                               */
/*********************************************/

#define	SHM_KEY		1808	/* shared memory key	 		*/

/*********************************************/
/* MACROS                                    */
/*********************************************/

#define CHILD_REQ( _ptr, cid) ( _ptr[cid].rqPerformed )

/*********************************************/
/* STRUCT DEFINITIONS                        */
/*********************************************/

struct cInfo {
  unsigned int rqPerformed;	/* number of requests performed */
  unsigned int rqFailed;	/* number of failed requests */
  time_t rqTime;		/* transfer time */
};

/*********************************************/
/* GLOBAL VARIABLES                          */
/*********************************************/

char *version 	= 	"0.11";
char *patchlvl	=	"0";
char *lastmod	=	"19-Jun-2002";
char *copyright =	"Copyright 1998 by Lars Hoss";
char replay[8192];

unsigned int httpd_port = 80;	/* default server port to use */
int fork_cds   = 5;		/* default num of childs to fork */
struct cInfo *_cInfo;
int shmid;			/* shared memory id */
time_t start_time;		/* time when the benchmark starts */

/*********************************************/
/* PROCEDURES                                */
/*********************************************/

void myInt()
/*
  Called by signal SIGINT.
  Outputs the benchmark result and frees the shared memory.
*/
  {
  int i;
  unsigned int total_req = 0;		
  unsigned int cds_req = 0;
  unsigned int min_req = 0;
  unsigned int max_req = 0;
  
  time_t total_time;

  total_time = time(0)-start_time;

  printf( "\nShutting down.\n\n" );

  total_req = 0;
  printf( "Duration: %d seconds\n", total_time );
  printf( "Number of childs: %d\n\n", fork_cds );
  printf( "Results:\n\n" );
  printf( "Child |       Hits |    Refused |   Hits/sec\n" ); 
  printf( "============================================\n" );
  max_req = CHILD_REQ( _cInfo, 0 );
  min_req = CHILD_REQ( _cInfo, 0 );
  for( i=0; i<fork_cds; i++ ) 
    {
    cds_req = CHILD_REQ( _cInfo, i );
    printf( "%' '5d | %' '10d | %' '10d | %' '10d\n", i+1, 
            cds_req, 0, cds_req / total_time );
    total_req += cds_req;
    if ( min_req > cds_req ) min_req = cds_req;
    if ( max_req < cds_req ) max_req = cds_req;
    }
  printf( "\nTotal hits: %d\n", total_req, total_time );
  printf( "Average hits/sec: %d\n\n", total_req / total_time );
  printf( "Average hits/sec: %d (per client)\n", 
           total_req / (total_time*fork_cds) );
  printf( "Minimum hits/sec: %d (per client)\n", min_req / total_time );
  printf( "Maximum hits/sec: %d (per client)\n", max_req / total_time );
  printf( "\n" );  
  /* at least free the shared memory */
  shmctl( shmid, IPC_RMID, 0L );
  exit(0);
  }

void childProc( int id, struct cInfo *_cIptr, const char *name, unsigned int hport )
/*
  This procedure is the main child function.
  It is called right after fork().
  It performes a request and stores the result in the shared
  memory segment.
*/
  {
  int sock;
  FILE *_stream;
  char buffer[255];
  unsigned int req;			/* number of requests performed */
  struct sockaddr_in server;
  struct hostent *_host_info;

  _host_info = gethostbyname( name );
  if( _host_info == 0L ) 
    {
    fprintf( stderr, "Invalid host name!\n" );
    exit(1);
    }
  server.sin_family = _host_info->h_addrtype;
  memcpy( (char*)&server.sin_addr, _host_info->h_addr,
                                   _host_info->h_length);
  server.sin_port = htons( hport );

  req = 0;
  while(1) 
    {
    sock = socket( AF_INET, SOCK_STREAM, 0 );
    if( sock < 0 ) 
      {
      perror( "error creating stream socket" );
      exit(1);
      }
    if( connect( sock, (struct sockaddr *)&server, sizeof( server ) ) < 0 ) 
      {
      perror( "connecting server" );
      exit(3);
      }

    /* now send replay file */
    write( sock, replay, sizeof(replay) ); 
    write( sock, "\n", 1 );
    _stream = fdopen( sock, "r+" );
    while( fgets( buffer, sizeof( buffer ), _stream ) != 0 );
    fclose( _stream );
    
    /* update num of performed requests in shared memory */
    req++;
    CHILD_REQ( _cIptr, id ) = req; 
    }
  }

void printUsage()
/*
  Just tell the user what to do ;-)
*/
  {
  printf( "Usage: swbench [OPTIONS] host\n\n" );
  printf( "  -h, --help    This page\n" );
  printf( "  -c, --childs  Number of childs to fork\n" );
  printf( "  -p, --port    Port to use\n" );
  printf( "  -r, --replay  file with content to be replayed\n" );
  printf( "  -v, --version Output information version and exit\n\n" );
  exit(0);
  }

void printVersion()
/*
  Tell something about me ;-)
*/
  {
  printf( "SWBench V%s.%s %s\n%s\n\n", version, patchlvl, lastmod, copyright );
  exit(0);
  }

/*********************************************/
/* MAIN                                      */
/*********************************************/

int main( int argc, char **argv )
  {
  int i;
  int b;
  int opts = 0;
  int seconds = 0;
  extern char *optarg;		/* used by getopt() parser */
  extern int opterr;		/* used by getopt() parser */
  extern int optind;
  struct hostent *_host_info; 	
  char *httpd_name = 0L;		
  char *replay_from;
  FILE *fp = 0L;
  int replay_flag = 0;
  
  opterr = 0;
  while( EOF != (i = getopt( argc, argv, "hvc:r:p:" ) ) ) 
    {
    opts++;
    switch(i) 
      {
      case 'v': printVersion();				break;
      case 'h': printUsage();				break;
      case 'c': fork_cds = atoi( optarg );		break;
      case 'p': httpd_port = atoi( optarg );		break;
      case 'r': replay_from = optarg; replay_flag=1;	break;
      }
    }
  httpd_name = argv[optind];	/* we asume this is the server name */
  if( httpd_name == NULL ) 
    {
    printUsage();
    exit(1);
    }

  /* check wether it is a valid name */
  _host_info = gethostbyname( httpd_name );
  if( _host_info == NULL ) 
    {
    fprintf( stderr, "Invalid host name!\n" );
    exit(1);
    }

  /* open replay file */
  if ( replay_flag == 1 ) 
    {
    fp = fopen( replay_from, "r" );
    if ( fp != 0L ) 
      {
      b = 0;
      while ( ( b < 8192 ) && ( ( replay[b++] = fgetc( fp ) ) != EOF ) );
      fclose( fp );
      }
    } 
  else 
    {
    /* initialize default replay file */
    replay[0] =	'G';
    replay[1] =	'E';
    replay[2] =	'T';
    replay[3] =	' ';
    replay[4] =	'/';
    replay[5] =	'\n';
    replay[6] =	0;
    }

  /* initialize shared memory for communication */
  shmid = shmget( SHM_KEY, sizeof( struct cInfo ) * fork_cds, IPC_CREAT | 0600 );
  if( shmid < 0 ) 
    {
    perror( "forktst: shmget failed:" );
    exit(1);
    }
  /* now attach the shared memory */
  _cInfo = (struct cInfo *)shmat( shmid, 0, 0 );
  if( _cInfo <= (struct cInfo *)(0) ) 
    {
    perror( "forktst: shmat failed:" );
    exit(1);
    }
  
  /* clear the shared memory segment */
  for( i=0; i<fork_cds; i++ ) 
    CHILD_REQ( _cInfo, i ) = 0;
  
  /* lets go! */
  fflush(stdout);
  start_time = time(0);
  for( i=0; i<fork_cds; i++ ) 
    {
    if( fork() == 0 ) 
      {
      childProc( i, _cInfo, httpd_name, httpd_port );
      exit(0);
      }
    }
  printf( "Benchmark for %s:%d is running ...\n", httpd_name, httpd_port );
  printf( "Started %d childs.\n", i );
  printf( "Press CTRL+C to stop.\n"); 

  /* set signal handler */
  signal( SIGINT, myInt );
  
  while(1) 
    {
    seconds++;
    printf("Running %5d Seconds\r",seconds);
    fflush(stdout);
    sleep(1);
    }
  printf("\n");
  return 0;
  }
