/*****************************************************************************/
/*
                                    WB.c

WASDbench :^) - an analogue to ApacheBench (AB, based on version 1.3d). Why
have it?  ApacheBench only compiles and runs on VMS 7.n and later.  This
version should compile and run for all supported WASD configurations.  It also
has the significant performance and granularity advantage (looks like ~25%) of
using the underlying $QIO services and not the socket API, and is AST event
driven rather than using the likes of select().  It is not a complete
implementation of AB (for instance, it currently does not support SSL).  The
CLI attempts to allow the same syntax as used by AB (within the constraint that
not all options are supported) so that it is relatively easy to switch between
the two (perhaps for comparison purposes) if desired.

Needless-to-say; putting this utility together was heavily influenced (and none
of the Math is mine) by ApacheBench :^)  Credits for that:

  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  This program is based on ZeusBench V1.0 written by Adam Twiss
  Copyright (c) 1996 by Zeus Technology Ltd.

  Copyright (c) 2000 The Apache Software Foundation.  All rights reserved.
  Including Mike Belshe, Michael Campanella, Dean Gaudet, Ralf S.Engelschall,
  Kurt Sussman, David N. Welton, and probably a host of others.
  See the prologue of AB.C for more detail.
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


POST METHOD
-----------
The /POST=<parameter> qualifier provides the content for a set of POSTed
requests.  The <parameter> can be a file specification in Unix path or VMS
syntax, a literal string (perhaps URL-form-encoded), or an exclamation point
and following  number (e.g. /POST="#32767") where a body of that size is
created dynamically and populated with alpha-numeric newline-terminated lines. 
The content can be anything acceptable (or unacceptable for that matter) to the
target URL.  By default it is sent with a content-type of "text/plain". 
Alternate content-type strings may be specified using the /CONTENT=<string>
qualifier.  The POSTed body is transfered in /SEGMENT=<integer> byte size
portions (default is 1024+128).  If the integer specified is zero the segment
size is varied with each network write (in an effort to 'exercise' the server).

The POST transfer can be "chunked" encoded by adding the /TRANSFER=CHUNKED
qualifier.  The chunk size defaults to 512+128 bytes but may be specified on
the command line using the optional /TRANSFER=CHUNKED=<integer> format.  If the
integer specified is zero the chunk size is varied with each chunk sent (in an
effort to 'exercise' the server).


A NOTE ON 'NODELACK'
--------------------
The /NODELACK (+n) qualifier applies the TCPIP$C_TCP_NODELACK (TCP_NODELACK)
TCP protocol option.  As the Compaq TCP/IP Services Programming Manual observes

"Under most circumstances, TCP sends data when presented.  When outstanding
data has not been acknowleged, TCP gathers small amounts of data into a single
packet and sends it when an acknowlegement is received.  This functionality can
cause significant delays for some clients ..."

and indeed does seem to introduce an unnecessary 200mS delay when using
keep-alive with WASD (and Apache) Bench(es).  Applying the /NODELACK (+n)
qualifier disables this acknowlgement delay - usually with spectacular
improvements in throughput, particularly where smaller responses are involved. 
The system-wide TCP/IP configuration change producing equivalent behaviour is

  $ TCPIP SET PROTOCOL TCP /NODELAY_ACK

(thanks to JFP for this information).


EXERCISE
--------
This functionality is not found in ApacheBench.  It is basically to supercede
similar functionality provided by the retired WWWRKOUT.  The "exercise" option
allows WASDbench to be used to stress-test a server.  This behaviour includes
mixing HEAD (~5%) with GET requests, and breaking requests during both request
and response transfers (~5%).  These are designed to shake up the server with
indeterminate request types and client error behaviours.  The best way to
utilize this stress-testing is wrap WASDbench with a DCL procedure providing a
variety of different requests types, quantities and concurrencies.

  $!(example "wrapper" procedure)
  $ IF P1 .EQS. "" THEN P1 = F$GETSYI("NODENAME")
  $ WB = "$HT_EXE:WB"
  $ SPAWN/NOWAIT WB +e +s +n -n 100 -c  5   
http://'p1'/wasd_root/exercise/0k.txt
  $ SPAWN/NOWAIT WB +e +s -k -n  50 -c  5 -k
http://'p1'/wasd_root/exercise/64k.txt
  $ SPAWN/NOWAIT WB +e +s    -n  50 -c  2    http://'p1'/cgi-bin/conan
  $!(delay spawning anymore until this one concludes)
  $              WB +e +s    -n 100 -c  5    http://'p1'/wasd_root/*.*
  $ SPAWN/NOWAIT WB +e +s -k -n  50 -c  5 -k
http://'p1'/wasd_root/exercise/64k.txt
  $ SPAWN/NOWAIT WB +e +s +n -n 100 -c  1    +c ISO-8859-1 -
                    http://'p1'/wasd_root/exercise/16k.txt
  $ SPAWN/NOWAIT WB +e +s    -n  10 -c  1    http://'p1'/cgi-bin/doesnt-exist
  $ SPAWN/NOWAIT WB +e +s -k -n  50 -c  2    http://'p1'/cgi-bin/conan/search
  $!(delay spawning anymore until this one concludes)
  $              WB +e +s    -n  50 -c  2    http://'p1'/wasd_root/src/httpd/*.*
  $ SPAWN/NOWAIT WB +e +s -k -n  50 -c  5 -k +l en,de,fr -
                    http://'p1'/wasd_root/doc/
  $ SPAWN/NOWAIT WB +e +s -k -n  50 -c  5 -k
http://'p1'/wasd_root/exercise/64k.txt
  $ SPAWN/NOWAIT WB +e +s -k -n  50 -c  5 -k http://'p1'/cgi-bin/cgi_symbols
  $              WB +e +s    -n 100 -c  5    +c ISO-8859-1 -
                    http://'p1'/wasd_root/*.*
  $!(etc.)


QUALIFIERS/SWITCHES
-------------------
/BUFFER=<integer>          response read buffer size
  (none)
/CHARSET=<string>          Accept-Charset string
  (none)
/CONCURRENCY=<integer>     number of simultaneous connections
  -C <integer>
/CONTENT=<string>          content-type when POSTing (defaults to text/plain)
  -T <string>
/DBUG                      enabled all (Debug) statements
  (none)
/DELAYS                    enable Nagle and acknowlegement delay (default off)
  (none)
/EAVESDROP                 show each response header and body 
  (none)                   (use with /SLIENT to just observe the response)
/ENCODING=<string>         adds a "Content-Encoding: <string>" header
  (none)
/EXERCISE                  exercise/test the server (see description above)
  (none)
/HEAD                      use the HTTP method HEAD
  -i 
/HELP                      display usage information
  -h
/KEEPALIVE                 maintains persistent connections (where possible)
  -k
/LANGUAGE=<string>         Accept-Language string
  (none)
/NOCONFIDENCE              do not show confidence warnings
  -S                       (yes, that's upper-case)
/NOHOST                    do not include a "Host: request field
  (none)
/NOPERCENTILES             do not show percentiles table
  -d
/NUMBER=<integer>          total number of connections benchmarked
  -n <integer>
/NODELACK                  disable delay acknowlegement
  (none)
/NONAGLE                   disable Nagle Algorithm (also see +D)
  (none)                   (yup, it's a plus and uppercase)
/PERSISTENT                maintains persistent connections (where possible)
  -k
/PIPELINE=<integer>        generate this many requests back-to-back
/POST=<parameter>          POST the contents of this literal/file
  -p <parameter>
/PROTOCOL=[1.1|1.0]        HTTP protocol (defaults to 1.1)
  (none)
/QUIET                     minimise output
  -q
/SEGMENT=<integer>         POST segment size (amount per write)
  (none)
/SILENT                    just make requests, no output (use when exercising)
  (none)
/TRANSFER=CHUNKED[=<integer>]  transfer encoding is chunked with optional size
 (none)
/VERSION                   print version information
  -V


BUILD DETAILS
-------------
See BUILD_WB.COM


COPYRIGHT
---------
Copyright (C) 1996-2020 Mark G.Daniel
This program, comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it under the
conditions of the GNU GENERAL PUBLIC LICENSE, version 3, or later version.
http://www.gnu.org/licenses/gpl.txt


VERSION HISTORY (update SOFTWAREVN as well!)
---------------
14-JUL-2015  MGD  v1.3.0, Bonne Fête Nationale!
                          q&d pipelined request generator
25-OCT-2009  MGD  v1.2.2, use IEEE floating-point on non-VAX
                          increase the fractional display for mS
                          add /EAVESDROP
11-FEB-2006  MGD  v1.2.1, refinements to connection/request statistics,
                          ReportTotals() prevent unwarranted SS$_BUGCHECK
17-OCT-2004  MGD  v1.2.0, add POST with content and transfer encoding,
                          remove the AB-like but WB-specific switches
                          (keeping only those that are AB-compatible)
11-JUL-2004  MGD  v1.1.0, make Nagle and acknowlegement delays off by default,
                          add /DELAYS and /NONAGLE qualifiers
23-DEC-2003  MGD  v1.0.1, minor conditional mods to support IA64
08-APR-2002  MGD  v1.0.0, initial development
*/
/*****************************************************************************/

#define SOFTWAREVN "1.3.0"
#define SOFTWARENM "WB"
#ifdef __ALPHA
#  define SOFTWAREID SOFTWARENM " AXP-" SOFTWAREVN
#endif
#ifdef __ia64
#  define SOFTWAREID SOFTWARENM " IA64-" SOFTWAREVN
#endif
#ifdef __VAX
#  define SOFTWAREID SOFTWARENM " VAX-" SOFTWAREVN
#endif
#ifdef __x86_64
#  define SOFTWAREID SOFTWARENM " X86-" SOFTWAREVN
#endif

#define COPYRIGHT_DATES "2002-2015"

#define COPYRIGHT_FULL \
"Copyright (C) 1996-2015 Mark G.Daniel\n\
This program, comes with ABSOLUTELY NO WARRANTY.\n\
This is free software, and you are welcome to redistribute it under the\n\
conditions of the GNU GENERAL PUBLIC LICENSE, version 3, or later version.\n\
http://www.gnu.org/licenses/gpl.txt\n"

/* standard C header files */
#include <ctype.h>
#include <errno.h>
#include <math.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unixio.h>

/* VMS related header files */
#include <descrip.h>
#include <iodef.h>
#include <libclidef.h>
#include <libdef.h>
#include <libdtdef.h>
#include <lib$routines.h>
#include <ssdef.h>
#include <stsdef.h>
#include <starlet.h>
#include <syidef.h>

/* Internet-related header files */
#include <socket.h>
#include <in.h>
#include <netdb.h>
#include <inet.h>

/* define required values from TCPIP$INETDEF.H (Multinet does not supply one)
*/
#define INET_PROTYP$C_STREAM 1
#define INETACP$C_TRANS 2
#define INETACP_FUNC$C_GETHOSTBYNAME 1
#define INETACP_FUNC$C_GETHOSTBYADDR 2
#define TCPIP$C_TCP 6
#define TCPIP$C_AF_INET 2
#define TCPIP$C_DSC_ALL 2
#define TCPIP$C_FULL_DUPLEX_CLOSE 8192
#define TCPIP$C_REUSEADDR 4
#define TCPIP$C_SOCK_NAME 4
#define TCPIP$C_SOCKOPT 1
#define TCPIP$C_TCPOPT 6
#define TCPIP$C_TCP_NODELAY 1
#define TCPIP$C_TCP_NODELACK 9

#define boolean int
#define true 1
#define false 0
 
/* this definition is not available in older VMS versions */
#ifndef EFN$C_ENF
#  define EFN$C_ENF 128  /* Event No Flag (no stored state) */
#endif

#define VMSok(x) ((x) & STS$M_SUCCESS)
#define VMSnok(x) !(((x) & STS$M_SUCCESS))

#define MINOF(a,b) ((a)<(b))?(a):(b)
#define MAXOF(a,b) ((a)>(b))?(a):(b)

#define MAX_READ_BUFFER_SIZE 16384
#define PIPELINE_MAX 32
#define VARY_READ_BUFFER_SIZE 24
#define HEARTBEAT_AT 100
#define DEFAULT_SERVER_PORT 80
#define DEFAULT_CONCURRENCY 10
#define DEFAULT_NUMBER 100
#define DEFAULT_POST_CONCURRENCY 1
#define DEFAULT_POST_NUMBER 1
#define DEFAULT_READ_BUFFER_SIZE 16384

/* lib$cvtf_from_internal_time() complains about about a zero elapsed time! */
#define LIB$_DELTIMREQ 1410052

struct AnIOsb
{
   unsigned short  Status;
   unsigned short  Count;
   unsigned long  Unused;
};

struct ConnectionStruct
{
   unsigned short  Channel;
   boolean  HeadMethod,
            PersistentConnect,
            PipelineRequest,
            PostMethod,
            ResponseHeader,
            ResponseReceived;
   int  RxCount,
        ContentCount,
        ContentLength,
        ConnectionNumber,
        PostContentCount,
        PostContentLength,
        ReadBufferSize,
        ResponseHeaderCount,
        ResponseHttpProtocol;
   char  *ChunkBufferPtr,
         *PostContentPtr;
   char  ReadBuffer [MAX_READ_BUFFER_SIZE+1];
   struct AnIOsb  IOsb;
   struct StatStruct  *StatPtr;
};

struct StatStruct
{
   int  ConnectionNumber,
        RxCount;
   unsigned long  StartBinTime [2],     /* start of request */
                  ConnectBinTime [2],   /* server connected */
                  EndWriteBinTime [2],  /* end of request write */
                  BeginReadBinTime [2], /* initial response */
                  EndBinTime [2];       /* end of request */
   float  ConnectTime,
          ProcessingTime,
          TotalTime,
          WaitTime;
};

#define MAX_CONNECTION_ERROR_COUNT 20

#define MIN_SENTINAL 99999.0

char  Utility [] = "WB";

unsigned long  CvtTimeFloatSeconds = LIB$K_DELTA_SECONDS_F;

#define DBUG 1
#if DBUG
boolean  Debug,
         DebugStats;
#else
#define Debug 0
#define DebugStats 0
#endif

boolean  DoExercise,
         DoEavesdrop,
         DoHeadMethod,
         DoPersistentConnect,
         DoPostMethod,
         DoQuietly,
         DoShowHelp,
         DoShowVersion,
         DoSilently,
         DoTransferEncodingChunked,
         DoVerbose,
         NoConfidence,
         NoDelayAck,
         NoDelayNagle,
         NoNoDelaysAtAll,
         NoHost,
         NoPercentiles;

int  BreakRequestCount,
     BreakResponseCount,
     BreakRequestLast,
     BreakResponseLast,
     CliConcurrency,
     CliNumber,
     CliPipeline = 1,
     ConnectionCount,
     ConnectionErrorCount,
     BrokenPipeCount,
     EfnEnf = EFN$C_ENF,
     FailedRequestCount,
     GetMethodCount,
     HeadMethodCount,
     HeadMethodLast,
     HeartBeatAt = HEARTBEAT_AT,
     HtmlTransfered,
     HttpProtocol = 11,
     HttpReadErrorCount,
     PersistentRequestCount,
     OutstandingCount,
     PostContentLength,
     PostMethodCount,
     PostSegmentSize = 1024+128,
     ProxyServerPort,
     ReadBufferSize,
     RequestCount,
     RequestGetLength,
     RequestHeadLength,
     RequestPostLength,
     ResponseDocumentLength,
     ServerPort,
     StatusCode100Count,
     TotalTransfered,
     TotalBytesSent,
     TransferEncodingChunkedSize = 512+128;

int  StatusCodeCount [6];

unsigned long  EndBinTime [2],
               StartBinTime [2];

char  *CliAcceptCharsetPtr,
      *CliAcceptLanguagePtr,
      *CliPostPtr,
      *CliProxyServerHostPtr,
      *HttpProtocolPtr = "HTTP/1.1",
      *PostContentPtr,
      *PostContentTypePtr = "text/plain",
      *PostContentEncodingPtr,
      *ServerHostPtr,
      *SpecifiedPathPtr,
      *TransferEncodingPtr = "";

char  CommandLine [256],
      HttpProxyServer [256],
      PostContentEncoding [256],
      PostTransferEncoding [256],
      RequestGetBuffer [PIPELINE_MAX * 512],
      RequestHeadBuffer [PIPELINE_MAX * 512],
      RequestPostBuffer [PIPELINE_MAX * 512],
      ResponseServerSoftware [256],
      ServerHost [256];

char *PostLine =
"0123456789 ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz\n";


struct StatStruct  *StatDataPtr;

$DESCRIPTOR (InetDeviceDsc, "UCX$DEVICE");

int  OptionEnabled = 1;

struct hostent  *ServerHostEntryPtr;
struct sockaddr_in  ServerSocketName;

struct {
   unsigned int  Length;
   char  *Address;
} ServerSocketNameItem =
   { sizeof(ServerSocketName), (void*)&ServerSocketName };

struct {
   unsigned short  Length;
   unsigned short  Parameter;
   char  *Address;
} ReuseAddr =
   { sizeof(OptionEnabled), TCPIP$C_REUSEADDR, (void*)&OptionEnabled },
  NoDelaysAtAll [] = {
    { sizeof(OptionEnabled), TCPIP$C_TCP_NODELAY, (void*)&OptionEnabled },
    { sizeof(OptionEnabled), TCPIP$C_TCP_NODELACK, (void*)&OptionEnabled },
  },
  NoDelAck =
   { sizeof(OptionEnabled), TCPIP$C_TCP_NODELACK, (void*)&OptionEnabled },
  NoDelay =
   { sizeof(OptionEnabled), TCPIP$C_TCP_NODELAY, (void*)&OptionEnabled },
  ReuseAddressSocketOption =
   { sizeof(ReuseAddr), TCPIP$C_SOCKOPT, (void*)&ReuseAddr },
  NoDelAckTcpOption =
   { sizeof(NoDelAck), TCPIP$C_TCPOPT, (void*)&NoDelAck },
  NoDelayTcpOption =
   { sizeof(NoDelay), TCPIP$C_TCPOPT, (void*)&NoDelay },
  NoDelaysAtAllTcpOption =
   { sizeof(NoDelaysAtAll), TCPIP$C_TCPOPT, (void*)&NoDelaysAtAll };

struct {
   unsigned short  Protocol;
   unsigned char  Type;
   unsigned char  Family;
} TcpSocket = { TCPIP$C_TCP, INET_PROTYP$C_STREAM, TCPIP$C_AF_INET };

/* function prototypes */
int CompareConnect (struct StatStruct*, struct StatStruct*);
int CompareProcessing (struct StatStruct*, struct StatStruct*);
int CompareResponse (struct StatStruct*, struct StatStruct*);
int CompareTotal (struct StatStruct*, struct StatStruct*);
void ConnectToServer (struct ConnectionStruct*);
void ConnectToServerAst (struct ConnectionStruct*);
void CloseConnection (struct ConnectionStruct*);
void DebugStatsData ();
void GetParameters (int, char**);
void GenerateConnections ();
void ParseURL ();
void PostRequestAst (struct ConnectionStruct*);
int ReadFileIntoMemory (char*, char**, int*);
void ReadResponseAst (struct ConnectionStruct*);
void ReportTotals ();
void ShowHelp ();
int strsame (char*, char*, int);
void WriteRequestAst (struct ConnectionStruct*);
char* ErrorMessage (int);

/*****************************************************************************/
/*
*/

int main
(
int argc,
char *argv[]
)
{
   static char  SyiVersion [8];
   static struct
   {
      short  buf_len;
      short  item;
      char   *buf_addr;
      short  *short_ret_len;
   }
   SyiItem [] =
   {
     { sizeof(SyiVersion), SYI$_VERSION, (char*)&SyiVersion, 0 },
     { 0,0,0,0 }
   };

   int  status,
        VmsVersion;
   char  *cptr, *sptr, *zptr;

   /*********/
   /* begin */
   /*********/

   if (getenv("WB$DBUG")) Debug = true;

   GetParameters (argc, argv);

   if (!CliNumber)
      if (CliPostPtr)
         CliNumber = DEFAULT_POST_NUMBER;
      else
         CliNumber = DEFAULT_NUMBER;

   if (!CliConcurrency)
      if (CliPostPtr)
         CliConcurrency = DEFAULT_POST_CONCURRENCY;
      else
         CliConcurrency = DEFAULT_CONCURRENCY;

   if (DoShowVersion)
   {
      fprintf (stdout, "%%%s-I-VERSION, %s\n%s",
               Utility, SOFTWAREID, COPYRIGHT_FULL);
      exit (SS$_NORMAL);
   }

   if (DoShowHelp) ShowHelp ();

   if (CliPipeline > PIPELINE_MAX)
   {
       fprintf (stdout, "%%%s-E-PIPELINE, maximum of %d\n",
                Utility, PIPELINE_MAX);
       exit (STS$K_ERROR | STS$M_INHIB_MSG);
   }
   if (CliPipeline > 1)
   {
      DoPersistentConnect = true;
      CliConcurrency /= CliPipeline;
      if (CliConcurrency <= 0) CliConcurrency = 1;
   }

   if (VMSnok (status = sys$getsyi (0, 0, 0, &SyiItem, 0, 0, 0)))
      exit (status);
   VmsVersion = ((SyiVersion[1]-48) * 10) + (SyiVersion[3]-48);
   if (Debug) fprintf (stdout, "VmsVersion: %d\n", VmsVersion);
   if (VmsVersion < 70) EfnEnf = 0;

   ParseURL ();

   if (CliPostPtr)
   {
      if (!PostSegmentSize && !TransferEncodingChunkedSize)
      {
         fprintf (stdout,
"%%%s-E-RANDOM, segment and chunk size cannot both be random (zero)\n",
                  Utility);
         exit (STS$K_ERROR | STS$M_INHIB_MSG);
      }

      cptr = CliPostPtr;
      if (*cptr != '/') while (*cptr && *cptr != ':') cptr++;
      if (*cptr == '/' || *(unsigned short*)cptr == ':[')
      {
         status = ReadFileIntoMemory (CliPostPtr, &PostContentPtr,
                                                  &PostContentLength);
         if (VMSnok (status)) exit (status);
      }
      else
      if (CliPostPtr[0] == '#' && isdigit(CliPostPtr[1]))
      {
         PostContentLength = atoi(CliPostPtr+1);
         if (!PostContentLength) exit (SS$_BADPARAM);
         PostContentPtr = calloc (1, PostContentLength+1);
         zptr = (sptr = PostContentPtr) + PostContentLength - 1;
         while (sptr < zptr)
            for (cptr = PostLine; *cptr && sptr < zptr; *sptr++ = *cptr++);
         *sptr++ = '\n';
         *sptr = '\0';
      }
      else
      {
         PostContentPtr = CliPostPtr;
         PostContentLength = strlen(PostContentPtr);
      }

      if (DoTransferEncodingChunked)
         sprintf (PostContentEncoding,
        /* emulate an OSX (Apple) WebDAV quirk */
"Transfer-Encoding: chunked\r\nX-Expected-Entity-Length: %u\r\n",
                  PostContentLength);

      if (PostContentEncodingPtr)
         sprintf (PostContentEncoding, "Content-Encoding: %s\r\n",
                  PostContentEncodingPtr); 
   }

   StatDataPtr = calloc (CliNumber, sizeof(struct StatStruct));

   if (!DoSilently)
   {
      fprintf (stdout,
"This is WASDbench :^) %s\n\
Copyright (C) %s Mark G.Daniel, http://wasd.vsm.com.au/\n\
\n",
         SOFTWAREID+3, COPYRIGHT_DATES);
      fflush (stdout);

      fprintf (stderr,
"Benchmarking %s (be patient)%s",
               ServerHostPtr, DoQuietly ? "..." : "\n");
   }

   GenerateConnections ();

   if (!DoSilently)
   {
      if (DoQuietly)
         fprintf (stdout, "..done\n");
      else
         fprintf (stderr, "Finished %d requests\n", ConnectionCount);
   }

   ReportTotals ();

   exit (SS$_NORMAL);
}

/*****************************************************************************/
/*
Queue up calls to initiate the specified number of concurrent connections. 
This is done with ASTs disabled so connection is actually initiated until this
function reenables ASTs.
*/
 
void GenerateConnections ()

{
   static boolean  randinit = false;
   static char  VaryingString [] = "abcdefghijklmnopqrstuvwxyz0123456789";

   int  cnt, status,
        Count,
        PipelineCount;
   char  AcceptCharsetBuffer [256],
         AcceptLanguageBuffer [256],
         ContentLengthBuffer [256],
         HostBuffer [256],
         PipelineBuffer [256];
   struct ConnectionStruct  *cxptr;

   /*********/
   /* begin */
   /*********/

   /* request header should vary in length */
   PipelineBuffer[0] = '\0';
   if (!randinit)
   {
      srand(time(NULL));
      randinit = true;
   }

   if (NoHost)
      HostBuffer[0] = '\0';
   else
      sprintf (HostBuffer, "Host: %s\r\n", ServerHostPtr);

   if (CliAcceptCharsetPtr)
      sprintf (AcceptCharsetBuffer, "Accept-Charset: %s\r\n",
               CliAcceptCharsetPtr);
   else
      AcceptCharsetBuffer[0] = '\0';

   if (CliAcceptLanguagePtr)
      sprintf (AcceptLanguageBuffer, "Accept-Language: %s\r\n",
               CliAcceptLanguagePtr);
   else
      AcceptLanguageBuffer[0] = '\0';

   sprintf (ContentLengthBuffer, "Content-Length: %d\r\n", PostContentLength);

   PipelineCount = 0;
   for (cnt = 1; cnt <= CliPipeline; cnt++)
   {
      if (CliPipeline > 1)
      {
         PipelineCount++;
         sprintf (PipelineBuffer, "X-Pipe-Line-%d: %.*s\r\n",
                  PipelineCount, (rand()%32)+1, VaryingString);
      }

      RequestGetLength += sprintf (RequestGetBuffer+RequestGetLength,
"GET %s%s %s\r\n\
User-Agent: WASDbench %s\r\n\
Accept: */*\r\n\
%s\
%s\
%s\
%s\
%s\
\r\n",
      HttpProxyServer, SpecifiedPathPtr, HttpProtocolPtr,
      SOFTWAREID+3,
      AcceptCharsetBuffer,
      AcceptLanguageBuffer,
      HostBuffer,
      PipelineBuffer,
      ((!CliPipeline && DoPersistentConnect) ||
       (CliPipeline && PipelineCount < CliPipeline)) ?
         "Connection: Keep-Alive\r\n" : "Connection: close\r\n");
   }
   if (Debug) fprintf (stdout, "|%s|\n", RequestGetBuffer);

   PipelineCount = 0;
   for (cnt = 1; cnt <= CliPipeline; cnt++)
   {
      if (CliPipeline > 1)
      {
         PipelineCount++;
         sprintf (PipelineBuffer, "X-Pipe-Line-%d: %.*s\r\n",
                  PipelineCount, (rand()%32)+1, VaryingString);
      }

      RequestHeadLength += sprintf (RequestHeadBuffer+RequestHeadLength,
"HEAD %s%s %s\r\n\
User-Agent: WASDbench %s\r\n\
Accept: */*\r\n\
%s\
%s\
%s\
%s\
%s\
\r\n",
      HttpProxyServer, SpecifiedPathPtr, HttpProtocolPtr,
      SOFTWAREID+3,
      AcceptCharsetBuffer,
      AcceptLanguageBuffer,
      HostBuffer,
      PipelineBuffer,
      ((!CliPipeline && DoPersistentConnect) ||
       (CliPipeline && PipelineCount < CliPipeline)) ?
         "Connection: Keep-Alive\r\n" : "Connection: close\r\n");
   }
   if (Debug) fprintf (stdout, "|%s|\n", RequestHeadBuffer);

   RequestPostLength += sprintf (RequestPostBuffer+RequestPostLength,
"POST %s%s %s\r\n\
User-Agent: WASDbench %s\r\n\
Content-Type: %s\r\n\
%s\
%s\
%s\
%s\
Accept: */*\r\n\
%s\
%s\
%s\
%s\
\r\n",
      HttpProxyServer, SpecifiedPathPtr, HttpProtocolPtr,
      SOFTWAREID+3,
      PostContentTypePtr,
      DoTransferEncodingChunked ? "" : ContentLengthBuffer,
      PostContentEncoding,
      TransferEncodingPtr,
      DoTransferEncodingChunked ? "Trailer: X-Content-Length\r\n" : "",
      AcceptCharsetBuffer,
      AcceptLanguageBuffer,
      HostBuffer,
      DoPersistentConnect ?
         "Connection: Keep-Alive\r\n" : "Connection: Close\r\n");
   if (Debug) fprintf (stdout, "|%s|\n", RequestPostBuffer);

   sys$gettim (&StartBinTime);

   /* disable user-mode ASTs until all initial connections are queued */
   sys$setast (0);
   for (Count = 0; Count < CliConcurrency; Count++)
   {
      cxptr = calloc (1, sizeof(struct ConnectionStruct));
      if (!cxptr) exit (vaxc$errno);
      status = sys$dclast (&ConnectToServer, cxptr, 0);
      if (VMSnok (status)) exit (status);
   }
   sys$setast (1);

   sys$hiber ();
}

/*****************************************************************************/
/*
Loop through the specified number of connections (or attempts thereof).  
Maintain, as best it can be, the specified number of simultaneous connections. 

If a connection attempt fails just quit there for that particular loop, leave 
a bit of time (as reads occur) to allow the server to recover.
*/
 
void ConnectToServer (struct ConnectionStruct *cxptr)

{
   int  status;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "ConnectToServer() cxptr: %d\n", cxptr);

   if (ConnectionCount >= CliNumber)
   {
      if (cxptr->Channel)
      {
         /* keep-alive will leave this channel connected */
         status = sys$dassgn (cxptr->Channel);
         if (Debug) fprintf (stdout, "sys$dassgn() %%X%08.08X\n", status);
      }
      if (!OutstandingCount) sys$wake (0, 0);
      return;
   }

   ConnectionCount++;
   OutstandingCount++;

   cxptr->ConnectionNumber = ConnectionCount;
   cxptr->StatPtr = &StatDataPtr[cxptr->ConnectionNumber-1];
   cxptr->StatPtr->ConnectionNumber = cxptr->ConnectionNumber;
   cxptr->ContentLength = -1;
   cxptr->ContentCount =
      cxptr->ResponseHeaderCount =
      cxptr->RxCount = 0;
   cxptr->HeadMethod =
      cxptr->PipelineRequest =
      cxptr->ResponseReceived = false;
   cxptr->ResponseHeader = true;

   if (DoExercise)
   {
      /* vary the size of the read buffer */
      cxptr->ReadBufferSize = ReadBufferSize;
      if (ConnectionCount % VARY_READ_BUFFER_SIZE)
         cxptr->ReadBufferSize /= ConnectionCount % VARY_READ_BUFFER_SIZE;
      if (cxptr->ReadBufferSize < 512) cxptr->ReadBufferSize = 512;
   }
   else
      cxptr->ReadBufferSize = ReadBufferSize;

   if (cxptr->PersistentConnect)
   {
      /* already connected! */
      sys$gettim (&cxptr->StatPtr->StartBinTime);
      memset (&cxptr->StatPtr->ConnectBinTime, 0, 8);
      memset (&cxptr->StatPtr->EndWriteBinTime, 0, 8);
      memset (&cxptr->StatPtr->BeginReadBinTime, 0, 8);
      memset (&cxptr->StatPtr->EndBinTime, 0, 8);
      /* fudge the initial connection status */
      cxptr->IOsb.Count = 0;
      cxptr->IOsb.Status = SS$_NORMAL;
      ConnectToServerAst (cxptr);
      return;
   }

   /***************************/
   /* initiate TCP connection */
   /***************************/

   /* assign a channel to the internet template device */
   if (VMSnok (status = sys$assign (&InetDeviceDsc, &cxptr->Channel, 0, 0)))
   {
      fprintf (stdout, "%%%s-E-CONNECT, assign() #%d\n",
               Utility, ConnectionCount);
      exit (status);
   }

   /* make the channel a TCP, connection-oriented socket */
   status = sys$qiow (0, cxptr->Channel, IO$_SETMODE, &cxptr->IOsb, 0, 0,
                      &TcpSocket, 0, 0, 0, &ReuseAddressSocketOption, 0);
   if (Debug)
      fprintf (stdout, "sys$qiow() %%X%08.08X IOsb %%X%08.08X\n",
               status, cxptr->IOsb.Status);
   if (VMSok (status) && VMSnok (cxptr->IOsb.Status))
      status = cxptr->IOsb.Status;
   if (VMSnok (status))
   {
      fprintf (stdout, "%%%s-E-SOCKOPT, sys$qiow() #%d\n",
               Utility, ConnectionCount);
      exit (status);
   }

   if (!NoNoDelaysAtAll)
   {
      /* turn off Nagle algorithm and acknowlegement delay (the default) */
      status = sys$qiow (0, cxptr->Channel, IO$_SETMODE, &cxptr->IOsb, 0, 0,
                         0, 0, 0, 0, &NoDelaysAtAllTcpOption, 0);
      if (Debug)
         fprintf (stdout, "sys$qiow() %%X%08.08X IOsb %%X%08.08X\n",
                  status, cxptr->IOsb.Status);
      if (VMSok (status) && VMSnok (cxptr->IOsb.Status))
         status = cxptr->IOsb.Status;
      if (VMSnok (status))
      {
         fprintf (stdout, "%%%s-E-TCPOPT, sys$qiow() #%d\n",
                  Utility, ConnectionCount);
         exit (status);
      }
   }

   if (NoDelayNagle)
   {
      /* turn off Nagle algorithm */
      status = sys$qiow (0, cxptr->Channel, IO$_SETMODE, &cxptr->IOsb, 0, 0,
                         0, 0, 0, 0, &NoDelayTcpOption, 0);
      if (Debug)
         fprintf (stdout, "sys$qiow() %%X%08.08X IOsb %%X%08.08X\n",
                  status, cxptr->IOsb.Status);
      if (VMSok (status) && VMSnok (cxptr->IOsb.Status))
         status = cxptr->IOsb.Status;
      if (VMSnok (status))
      {
         fprintf (stdout, "%%%s-E-TCPOPT, sys$qiow() #%d\n",
                  Utility, ConnectionCount);
         exit (status);
      }
   }

   if (NoDelayAck)
   {
      /* turn off acknowlegement delay */
      status = sys$qiow (0, cxptr->Channel, IO$_SETMODE, &cxptr->IOsb, 0, 0,
                         0, 0, 0, 0, &NoDelAckTcpOption, 0);
      if (Debug)
         fprintf (stdout, "sys$qiow() %%X%08.08X IOsb %%X%08.08X\n",
                  status, cxptr->IOsb.Status);
      if (VMSok (status) && VMSnok (cxptr->IOsb.Status))
         status = cxptr->IOsb.Status;
      if (VMSnok (status))
      {
         fprintf (stdout, "%%%s-E-TCPOPT, sys$qiow() #%d\n",
                  Utility, ConnectionCount);
         exit (status);
      }
   }

   sys$gettim (&cxptr->StatPtr->StartBinTime);
   memset (&cxptr->StatPtr->ConnectBinTime, 0, 8);
   memset (&cxptr->StatPtr->EndWriteBinTime, 0, 8);
   memset (&cxptr->StatPtr->BeginReadBinTime, 0, 8);
   memset (&cxptr->StatPtr->EndBinTime, 0, 8);

   status = sys$qio (EfnEnf, cxptr->Channel, IO$_ACCESS, &cxptr->IOsb,
                     &ConnectToServerAst, cxptr,
                     0, 0, &ServerSocketNameItem, 0, 0, 0);
   if (VMSnok (status))
   {
      fprintf (stdout, "%%%s-E-CONNECT, sys$qio() #%d\n",
               Utility, cxptr->ConnectionNumber);
      exit (status);
   }
}

/*****************************************************************************/
/*
Connection to server has completed (either successfully or unsuccessfully).
*/
 
void ConnectToServerAst (struct ConnectionStruct *cxptr)

{
   int  status;
   unsigned long  BinTime [2];
   unsigned short  NumTime [7];

   /*********/
   /* begin */
   /*********/

   if (Debug)
      fprintf (stdout, "ConnectToServerAst() IOsb %%X%08.08X\n",
               cxptr->IOsb.Status);

   status = cxptr->IOsb.Status;

   RequestCount++;

   if (VMSnok (status))
   {
      /*****************/
      /* connect error */
      /*****************/

      fprintf (stdout, "%%%s-W-CONNECT, sys$qio() #%d %s\n",
               Utility, cxptr->ConnectionNumber, ErrorMessage(status));
      FailedRequestCount++;
      if (ConnectionErrorCount++ > MAX_CONNECTION_ERROR_COUNT)
      {
         ReportTotals ();
         exit (SS$_NORMAL);
      }

      /* close current then re-initiate a new connection */
      cxptr->PersistentConnect = false;
      CloseConnection (cxptr);
      ConnectToServer (cxptr);
      return;
   }

   sys$gettim (&cxptr->StatPtr->ConnectBinTime);

   /***************/
   /* exercising? */
   /***************/

   if (DoExercise)
   {
      sys$gettim (&BinTime);
      sys$numtim (&NumTime, &BinTime);
      if (NumTime[6] != BreakRequestLast && !(NumTime[6] % 26))
      {
         BreakRequestLast = NumTime[6];
         BreakRequestCount++;
         /* ensure the network connection is dropped */
         cxptr->PersistentConnect = false;
         CloseConnection (cxptr);
         ConnectToServer (cxptr);
         return;
      }
   }

   if (cxptr->PipelineRequest)
   {
      /*********************/
      /* pipelined request */
      /*********************/

      /* request header(s) already sent */
      if (cxptr->HeadMethod)
         HeadMethodCount++;
      else
      if (cxptr->PostMethod)
         PostMethodCount++;
      else
         GetMethodCount++;

      status = sys$dclast (&WriteRequestAst, cxptr, 0);
      if (VMSnok (status)) exit (status);

      return;
   }

   if (CliPipeline) cxptr->PipelineRequest = true;

   /****************/
   /* send request */
   /****************/

   if (DoExercise)
   {
      sys$gettim (&BinTime);
      sys$numtim (&NumTime, &BinTime);
      if (NumTime[6] != HeadMethodLast && !(NumTime[6] % 6))
      {
         HeadMethodLast = NumTime[6];
         cxptr->HeadMethod = true;
      }
   }
   else
   {
      cxptr->HeadMethod = DoHeadMethod;
      cxptr->PostMethod = DoPostMethod;
   }

   if (cxptr->HeadMethod)
   {
      HeadMethodCount++;
      TotalBytesSent += RequestHeadLength;
      status = sys$qio (EfnEnf, cxptr->Channel,
                        IO$_WRITEVBLK, &cxptr->IOsb, WriteRequestAst, cxptr,
                        RequestHeadBuffer, RequestHeadLength, 0, 0, 0, 0);
   }
   else
   if (cxptr->PostMethod)
   {
      PostMethodCount++;
      TotalBytesSent += RequestPostLength;
      status = sys$qio (EfnEnf, cxptr->Channel,
                        IO$_WRITEVBLK, &cxptr->IOsb, WriteRequestAst, cxptr,
                        RequestPostBuffer, RequestPostLength, 0, 0, 0, 0);
   }
   else
   {
      GetMethodCount++;
      TotalBytesSent += RequestGetLength;
      status = sys$qio (EfnEnf, cxptr->Channel,
                        IO$_WRITEVBLK, &cxptr->IOsb, WriteRequestAst, cxptr,
                        RequestGetBuffer, RequestGetLength, 0, 0, 0, 0);
   }

   if (VMSnok (status))
   {
      fprintf (stdout, "%%%s-E-WRITEVBLK, (request) sys$qio() #%d\n",
               Utility, cxptr->ConnectionNumber);
      exit (status);
   }
}

/*****************************************************************************/
/*
Request header write has completed (either successfully or unsuccessfully).
*/
 
void WriteRequestAst (struct ConnectionStruct *cxptr)

{
   int  status;

   /*********/
   /* begin */
   /*********/

   if (Debug)
      fprintf (stdout, "WriteRequestAst() IOsb: %%X%08.08X\n",
               cxptr->IOsb.Status);

   sys$gettim (&cxptr->StatPtr->EndWriteBinTime);

   status = cxptr->IOsb.Status;

   if (VMSnok (status))
   {
      /***************/
      /* write error */
      /***************/

      if (status == SS$_LINKDISCON) BrokenPipeCount++;

      fprintf (stdout, "%%%s-W-WRITE, sys$qio() #%d %s\n",
               Utility, cxptr->ConnectionNumber, ErrorMessage(status));
      FailedRequestCount++;

      /* close current then re-initiate a new connection */
      cxptr->PersistentConnect = false;
      CloseConnection (cxptr);
      ConnectToServer (cxptr);
      return;
   }

   if (cxptr->PostMethod)
   {
      /***************/
      /* POST method */
      /***************/

      cxptr->PostContentCount = 0;
      cxptr->PostContentLength = PostContentLength;
      cxptr->PostContentPtr = PostContentPtr;

      /* fudge this first one */
      cxptr->IOsb.Status = SS$_NORMAL;

      PostRequestAst (cxptr);
      return;
   }

   /*****************/
   /* read response */
   /*****************/

   status = sys$qio (EfnEnf, cxptr->Channel, IO$_READVBLK, &cxptr->IOsb,
                     ReadResponseAst, cxptr,
                     cxptr->ReadBuffer, cxptr->ReadBufferSize, 0, 0, 0, 0);
   if (VMSnok (status))
   {
      fprintf (stdout, "%%%s-E-READVBLK, sys$qio() #%d\n",
               Utility, cxptr->ConnectionNumber);
      exit (status);
   }
}

/*****************************************************************************/
/*
Request POST body write has completed (either successfully or unsuccessfully).
*/
 
void PostRequestAst (struct ConnectionStruct *cxptr)

{
   static unsigned long  RandomMask;
   static unsigned long  BinTime [2];

   int  status,
        ChunkSize,
        ContentCount,
        DataCount,
        SegmentSize;
   char  *sptr,
         *DataPtr;

   /*********/
   /* begin */
   /*********/

   if (Debug)
      fprintf (stdout, "PostRequestAst() IOsb: %%X%08.08X %d %d 0x%08.08x\n",
               cxptr->IOsb.Status, cxptr->PostContentLength,
               cxptr->PostContentCount, cxptr->PostContentPtr);

   sys$gettim (&cxptr->StatPtr->EndWriteBinTime);

   status = cxptr->IOsb.Status;

   if (VMSnok (status))
   {
      /***************/
      /* write error */
      /***************/

      if (status == SS$_LINKDISCON) BrokenPipeCount++;

      fprintf (stdout, "%%%s-W-WRITE, sys$qio() #%d %s\n",
               Utility, cxptr->ConnectionNumber, ErrorMessage(status));
      FailedRequestCount++;

      /* close current then re-initiate a new connection */
      cxptr->PersistentConnect = false;
      CloseConnection (cxptr);
      ConnectToServer (cxptr);
      return;
   }


   if (cxptr->PostContentCount < cxptr->PostContentLength)
   {
      /*************/
      /* POST body */
      /*************/

      if (!BinTime[0])
      {
         /* init the random chunk/segment size generator */
         sys$gettim (&BinTime);
         srand (BinTime[0]);
         /* create a mask one order larger than the segment size */
         if (!(SegmentSize = PostSegmentSize)) SegmentSize = 2048+128;
         for (RandomMask = 1;
              SegmentSize /= 2;
              RandomMask = (RandomMask << 1) | 1);
      }

      if (PostSegmentSize)
         SegmentSize = PostSegmentSize;
      else
      {
         /* create a random segment size */
         SegmentSize = rand() & RandomMask;
         if (!SegmentSize) SegmentSize = 250;
      }

      if (DoTransferEncodingChunked)
      {
         if (!cxptr->ChunkBufferPtr)
         {
            if (PostSegmentSize)
               cxptr->ChunkBufferPtr = calloc (1, PostSegmentSize+256);
            else
               cxptr->ChunkBufferPtr = calloc (1, 2048+128+256);
            if (!cxptr->ChunkBufferPtr) exit (vaxc$errno);
         }

         if (TransferEncodingChunkedSize)
            ChunkSize = TransferEncodingChunkedSize;
         else
         {
            /* create a random chunk size */
            ChunkSize = rand() & RandomMask;
            if (!ChunkSize) ChunkSize = 250;
         }

         ContentCount = cxptr->PostContentLength - cxptr->PostContentCount;
         if (ContentCount > SegmentSize) ContentCount = SegmentSize;
         if (ChunkSize > ContentCount) ChunkSize = ContentCount;
         ContentCount = ChunkSize;

         /* populate the chunk buffer */
         sptr = cxptr->ChunkBufferPtr;
         sptr += sprintf (sptr, "%x; this is ignored\r\n", ChunkSize);
         memcpy (sptr, cxptr->PostContentPtr, ChunkSize);
         sptr += ChunkSize;
         *sptr++ = '\r';
         *sptr++ = '\n';

         if (cxptr->PostContentCount + ContentCount >=
             cxptr->PostContentLength)
         {
            /* add a zero length chunk, trailer field and finalizing line */
            sptr += sprintf (sptr,
"0; this is ignored\r\nX-Content-Length: %d\r\n\r\n",
                             cxptr->PostContentLength);
         }

         DataPtr = cxptr->ChunkBufferPtr;
         DataCount = sptr - cxptr->ChunkBufferPtr;

      }
      else
      {
         ContentCount = cxptr->PostContentLength - cxptr->PostContentCount;
         if (ContentCount > SegmentSize) ContentCount = SegmentSize;
         DataPtr = cxptr->PostContentPtr;
         DataCount = ContentCount;
      }

      TotalBytesSent += DataCount;
      status = sys$qio (EfnEnf, cxptr->Channel,
                        IO$_WRITEVBLK, &cxptr->IOsb, PostRequestAst, cxptr,
                        DataPtr, DataCount, 0, 0, 0, 0);

      if (VMSnok (status))
      {
         fprintf (stdout, "%%%s-E-WRITEVBLK, (body) sys$qio() #%d\n",
                  Utility, cxptr->ConnectionNumber);
         exit (status);
      }

      cxptr->PostContentPtr += ContentCount;
      cxptr->PostContentCount += ContentCount;

      return;
   }

   /*****************/
   /* read response */
   /*****************/

   status = sys$qio (EfnEnf, cxptr->Channel, IO$_READVBLK, &cxptr->IOsb,
                     ReadResponseAst, cxptr,
                     cxptr->ReadBuffer, cxptr->ReadBufferSize, 0, 0, 0, 0);
   if (VMSnok (status))
   {
      fprintf (stdout, "%%%s-E-READVBLK, sys$qio() #%d\n",
               Utility, cxptr->ConnectionNumber);
      exit (status);
   }
}

/*****************************************************************************/
/*
A read from the server has completed.  Check it's status.  If complete (or
disconnected) then report.  If not complete then queue to read more.  This code
assumes that at the very least if the header is not written as a whole then it
is written as complete lines.
*/
 
void ReadResponseAst (struct ConnectionStruct *cxptr)

{
   int  status;
   unsigned long  BinTime [2];
   unsigned short  NumTime [7];
   char  *cptr, *lptr, *sptr, *zptr;

   /*********/
   /* begin */
   /*********/

   if (Debug)
      fprintf (stdout, "ReadResponseAst() IOsb: %%X%08.08X Bytes: %d\n",
               cxptr->IOsb.Status, cxptr->IOsb.Count);

   if (!cxptr->RxCount) sys$gettim (&cxptr->StatPtr->BeginReadBinTime);

   status = cxptr->IOsb.Status;

   if (VMSnok (status))
   {
      /*********/
      /* error */
      /*********/

      if (status != SS$_LINKDISCON)
      {
         /**************/
         /* read error */
         /**************/

         fprintf (stdout, "%%%s-W-READ, sys$qio() #%d (%d bytes) %s\n",
                  Utility, cxptr->ConnectionNumber, cxptr->RxCount,
                  ErrorMessage(status));
         HttpReadErrorCount++;
      }

      /* close current then re-initiate a new connection */
      cxptr->PersistentConnect = false;
      CloseConnection (cxptr);
      ConnectToServer (cxptr);
      return;
   }

   /********************/
   /* process response */
   /********************/

   if (DoEavesdrop)
      fprintf (stdout, "%*.*s",
               cxptr->IOsb.Count, cxptr->IOsb.Count, cxptr->ReadBuffer);

   if (!cxptr->ResponseReceived)
   {
      /********************/
      /* initial response */
      /********************/

      /* different assumptions for different protocols */
      if (HttpProtocol == 11)
         cxptr->PersistentConnect = true;
      else
         cxptr->PersistentConnect = false;

      cxptr->ReadBuffer[cxptr->IOsb.Count] = '\0';
      cptr = cxptr->ReadBuffer;
      if (!memcmp (cptr, "HTTP/1.1", 8))
      {
         cxptr->ResponseHttpProtocol = 11;
         cptr += 8;
      }
      else
      if (!memcmp (cptr, "HTTP/1.0", 8))
      {
         cxptr->ResponseHttpProtocol = 10;
         cptr += 8;
      }
      while (*cptr && *cptr != ' ' && *cptr != '\r' && *cptr != '\n') cptr++;
      while (*cptr == ' ' && *cptr != '\r' && *cptr != '\n') cptr++;

      /* increment the counter corresponding to the first digit */
      if (isdigit(*cptr) && *cptr >= '1' && *cptr <= '5')
      {
         /* if it's not a "100 continue" the consider it the HTTP response */
         if (memcmp (cptr, "100", 3))
         {
            StatusCodeCount[*cptr-'0']++;
            cxptr->ResponseReceived = true;
         }
         else
            StatusCode100Count++;
      }
      else
      {
         StatusCodeCount[0]++;
         cxptr->ResponseReceived = true;
      }
   }

   cxptr->RxCount += cxptr->IOsb.Count;

   if (cxptr->ResponseReceived &&
       cxptr->ResponseHeader)
   {
      /*******************/
      /* response header */
      /*******************/

      cxptr->ReadBuffer[cxptr->IOsb.Count] = '\0';
      cptr = cxptr->ReadBuffer;
      while (*cptr)
      {
         lptr = cptr;
         if (toupper(*cptr) == 'C' && strsame (cptr, "Connection:", 11))
         {
            cptr += 11;
            while (isspace(*cptr) && *cptr != '\r' && *cptr != '\n') cptr++;
            if (strsame (cptr, "close", 5))
               cxptr->PersistentConnect = false;
            else
            if (strsame (cptr, "Keep-Alive", 10))
               cxptr->PersistentConnect = true;
         }
         else
         if (toupper(*cptr) == 'C' && strsame (cptr, "Content-Length:", 15))
         {
            cptr += 15;
            while (isspace(*cptr) && *cptr != '\r' && *cptr != '\n') cptr++;
            cxptr->ContentLength = atoi(cptr);
            if (!ResponseDocumentLength)
               ResponseDocumentLength = cxptr->ContentLength;
         }
         else
         if (toupper(*cptr) == 'K' && strsame (cptr, "Keep-Alive:", 11))
         {
            cptr += 11;
            cxptr->PersistentConnect = true;
         }
         else
         if (!ResponseServerSoftware[0] &&
             toupper(*cptr) == 'S' && strsame (cptr, "Server:", 7))
         {
            cptr += 7;
            while (isspace(*cptr) && *cptr != '\r' && *cptr != '\n') cptr++;
            zptr = (sptr = ResponseServerSoftware) +
                   sizeof(ResponseServerSoftware)-1;
            while (*cptr && *cptr != '\r' && *cptr != '\n' && sptr < zptr)
               *sptr++ = *cptr++;
            *sptr = '\0';
         }
         while (*cptr && *cptr != '\r' && *cptr != '\n') cptr++;
         if (*cptr == '\r') cptr++;
         if (*cptr == '\n') cptr++;
         cxptr->ResponseHeaderCount += cptr - lptr;
         if (!*cptr) break;
         if (*(unsigned short*)cptr == '\r\n' || *cptr == '\n' )
         {
            if (cxptr->ContentLength < 0) cxptr->PersistentConnect = false;
            if (*cptr == '\r') cxptr->ResponseHeaderCount++;
            cxptr->ResponseHeaderCount++;
            cxptr->ResponseHeader = false;
            cxptr->ContentCount = cxptr->RxCount - cxptr->ResponseHeaderCount;
            break;
         }
      }
   }
   else
      cxptr->ContentCount += cxptr->IOsb.Count;

   if (Debug)
      fprintf (stdout, "keepalive:%d count:%d length:%d\n",
               cxptr->PersistentConnect, cxptr->ContentCount,
               cxptr->ContentLength);

   if (cxptr->ContentCount > ResponseDocumentLength)
      ResponseDocumentLength = cxptr->ContentCount;

   if (cxptr->HeadMethod ||
       (cxptr->ContentLength >= 0 &&
        cxptr->ContentCount >= cxptr->ContentLength))
   {
      CloseConnection (cxptr);
      ConnectToServer (cxptr);
      return;
   }

   /***************/
   /* exercising? */
   /***************/

   if (DoExercise)
   {
      sys$gettim (&BinTime);
      sys$numtim (&NumTime, &BinTime);
      if (NumTime[6] != BreakResponseLast && !(NumTime[6] % 29))
      {
         BreakResponseLast = NumTime[6];
         BreakResponseCount++;
         /* ensure the network connection is dropped */
         cxptr->PersistentConnect = false;
         CloseConnection (cxptr);
         ConnectToServer (cxptr);
         return;
      }
   }

   /**********************/
   /* read more response */
   /**********************/

   status = sys$qio (EfnEnf, cxptr->Channel, IO$_READVBLK, &cxptr->IOsb,
                     ReadResponseAst, cxptr,
                     cxptr->ReadBuffer, cxptr->ReadBufferSize, 0, 0, 0, 0);
   if (VMSnok (status))
   {
      fprintf (stdout, "%%%s-E-READVBLK, sys$qio() #%d\n",
               Utility, cxptr->ConnectionNumber);
      exit (status);
   }
}

/****************************************************************************/
/*
Request is concluded.  If keep-alive then basically do nothing.  If not
keep-alive then close the connection by deassigning the channel.
*/

void CloseConnection (struct ConnectionStruct *cxptr)

{
   int  status;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "CloseConnection() %d\n", cxptr->Channel);

   sys$gettim (&cxptr->StatPtr->EndBinTime);

   if (!DoSilently)
      if (!DoQuietly && HeartBeatAt && (!(RequestCount % HeartBeatAt)))
      {
         fprintf (stderr, "Completed %d requests\n", RequestCount);
         HeartBeatAt += HEARTBEAT_AT;
      }

   TotalTransfered += cxptr->RxCount;
   cxptr->StatPtr->RxCount += cxptr->RxCount;
   HtmlTransfered += cxptr->RxCount - cxptr->ResponseHeaderCount;

   if (OutstandingCount) OutstandingCount--;

   if (cxptr->PersistentConnect)
   {
      PersistentRequestCount++;
      return;
   }

   status = sys$dassgn (cxptr->Channel);
   if (Debug) fprintf (stdout, "sys$dassgn() %%X%08.08X\n", status);
   cxptr->Channel = 0;
}

/*****************************************************************************/
/*
Prase the CLI specified URL into it's host-name (and resolved address), port
and path.
*/
 
void ParseURL ()

{
   int  status;
   char  *cptr, *sptr;

   /*********/
   /* begin */
   /*********/

   cptr = SpecifiedPathPtr;
   if (strsame (cptr, "http://", 7))
   {
      cptr += 7;
      sptr = ServerHost;
      while (*cptr && *cptr != ':' && *cptr != '/') *sptr++ = *cptr++;
      *sptr = '\0';
      if (*cptr == ':')
      {
         cptr++;
         if (isdigit(*cptr)) ServerPort = atoi(cptr);
         while (*cptr && isdigit(*cptr)) cptr++;
      }
      SpecifiedPathPtr = cptr;
   }
   if (!ServerHost[0])
   {
      /* assume the local host is the server */
      if (gethostname (ServerHost, sizeof(ServerHost)))
         status = SS$_NOSUCHNODE;
      if (VMSnok (status))
      {
         fprintf (stdout, "%%%s-E-SERVER, \"%s\" %s\n",
                  Utility, ServerHost, ErrorMessage(status));
         exit (STS$K_ERROR | STS$M_INHIB_MSG);
      }
      if (Debug) fprintf (stdout, "ServerHost |%s|\n", ServerHost);
   }
   if (!ServerPort) ServerPort = 80;
   if (Debug)
      fprintf (stdout, "|%s|%d|%s|\n",
               ServerHost, ServerPort, SpecifiedPathPtr);
   ServerHostPtr = ServerHost;

   ServerHostEntryPtr = gethostbyname (cptr = ServerHost);
   if (!ServerHostEntryPtr)
   {
      status = vaxc$errno;
      fprintf (stdout, "%%%s-E-SERVER, \"%s\"\n", Utility, cptr);
      exit (status);
   }
   ServerSocketName.sin_family = ServerHostEntryPtr->h_addrtype;
   ServerSocketName.sin_port = htons (ServerPort);
   ServerSocketName.sin_addr = *((struct in_addr *)ServerHostEntryPtr->h_addr);

   if (strchr (ServerHostEntryPtr->h_name, '.'))
      strcpy (cptr, ServerHostEntryPtr->h_name);
   if (Debug) fprintf (stdout, "server/proxy |%s|\n", cptr);
   /* looks better if its all in lower case (host name is sometimes upper) */
   for (/* above */; *cptr; cptr++) *cptr = tolower(*cptr);
}
 
/*****************************************************************************/
/*
Report the statistics.  This is based on the format and math used with Apache
Bench.  Float is used extensively with this WASD version, to improve resolution
and to reduce computation issues.
*/
 
void ReportTotals ()

{
   int  cnt, status,
        MilliSeconds,
        Non2xxResponseCount;
   unsigned long  ConnectBinTime [2],
                  ElapsedBinTime [2],
                  ProcessingBinTime [2],
                  WaitBinTime [2],
                  TotalBinTime [2];
   float  ConnectStdDev = 0.0,
          ConnectTimeMin = MIN_SENTINAL,
          ConnectTimeMax = 0.0,
          ConnectTimeMedian = 0.0,
          ConnectTimeTotal = 0.0,
          ElapsedTime = 0.0,
          KbytesPerSecond = 0.0,
          ProcessingStdDev = 0.0,
          ProcessingTimeMax = 0.0,
          ProcessingTimeMedian = 0.0,
          ProcessingTimeMin = MIN_SENTINAL,
          ProcessingTimeTotal = 0.0,
          RequestsPerSecond = 0.0,
          TimePerRequestConc = 0.0,
          TimePerRequestMean = 0.0,
          TotalStdDev = 0.0,
          TotalTimeMax = 0.0,
          TotalTimeMedian = 0.0,
          TotalTimeMin = MIN_SENTINAL,
          TotalTimeTotal = 0.0,
          WaitStdDev = 0.0,
          WaitTimeMax = 0.0,
          WaitTimeMedian = 0.0,
          WaitTimeMin = MIN_SENTINAL,
          WaitTimeTotal = 0.0;
   struct StatStruct  *sdptr;

   /*********/
   /* begin */
   /*********/

   if (DoSilently) return;

   sys$gettim (&EndBinTime);

   status = lib$sub_times (&EndBinTime, &StartBinTime, &ElapsedBinTime);
   if (VMSnok (status)) exit (status);

#ifdef __ia64
   /* lib$cvts_...() is not supported on Alpha V7.3-2 or earlier */
   status = lib$cvts_from_internal_time (&CvtTimeFloatSeconds,
                                         &ElapsedTime,
                                         &ElapsedBinTime);
#else
   status = lib$cvtf_from_internal_time (&CvtTimeFloatSeconds,
                                         &ElapsedTime,
                                         &ElapsedBinTime);
#endif
   if (VMSnok (status)) exit (status);

   if (ElapsedTime > 0.0)
   {
      KbytesPerSecond = ((float)(TotalTransfered + TotalBytesSent) /
                         ElapsedTime / 1024);

      RequestsPerSecond = (float)ConnectionCount / ElapsedTime;
   }
   else
      KbytesPerSecond = RequestsPerSecond = 0.0;

   if (ConnectionCount > 0)
      TimePerRequestConc =
         ((float)CliConcurrency * ElapsedTime / (float)ConnectionCount)
             * 1000.0;
   else
      TimePerRequestConc = 0.0;

   if (ConnectionCount > 0)
      TimePerRequestMean = (ElapsedTime / (float)ConnectionCount) * 1000.0;
   else
      TimePerRequestMean = 0.0;

   Non2xxResponseCount = StatusCodeCount[0] +
                         StatusCodeCount[1] +
                         StatusCodeCount[3] +
                         StatusCodeCount[4] +
                         StatusCodeCount[5];

   MilliSeconds = (int)(ElapsedTime * 1000.0);

   fprintf (stdout,
"Server Software:        %s\n\
Server Hostname:        %s\n\
Server Port:            %d\n\
\n\
Document Path:          %s\n\
Document Length:        %d bytes\n\
\n\
Concurrency Level:      %d\n\
Time taken for tests:   %.3f seconds\n\
Complete requests:      %d\n\
Failed requests:        %d\n\
Broken pipe errors:     %d\n",
   ResponseServerSoftware,
   ServerHost, ServerPort,
   SpecifiedPathPtr,
   ResponseDocumentLength,
   CliConcurrency,
   ElapsedTime,
   RequestCount - FailedRequestCount,
   FailedRequestCount,
   BrokenPipeCount);

   if (DoExercise)
   {
      fprintf (stdout,
"GET requests:           %d (%d%%)\n\
HEAD requests:          %d (%d%%)\n\
POST requests:          %d (%d%%)\n\
Request breaks:         %d (%d%%)\n\
Response breaks:        %d (%d%%)\n",
         GetMethodCount, GetMethodCount * 100 / ConnectionCount,
         HeadMethodCount, HeadMethodCount * 100 / ConnectionCount,
         PostMethodCount, PostMethodCount * 100 / ConnectionCount,
         BreakRequestCount, BreakRequestCount * 100 / ConnectionCount,
         BreakResponseCount, BreakResponseCount * 100 / ConnectionCount);
   }

   if (Non2xxResponseCount)
      fprintf (stdout,
"Non-2xx responses:      %d\n", Non2xxResponseCount);

   if (DoPersistentConnect)
      fprintf (stdout,
"Keep-Alive requests:    %d\n", PersistentRequestCount);

   fprintf (stdout,
"Response count:         1nn:%d (100:%d) 2nn:%d 3nn:%d 4nn:%d 5nn:%d err:%d\n\
Total transferred:      %d bytes\n\
HTML transferred:       %d bytes\n\
Requests per second:    %.1f [#/sec] (mean)\n\
Time per request:       %.3f [mS] (mean)\n\
Time per request:       %.3f [mS] (mean, across all concurrent requests)\n\
Transfer rate:          %.1f [kBytes/S] received\n\
",
   StatusCodeCount[1], StatusCode100Count,
   StatusCodeCount[2], StatusCodeCount[3],
   StatusCodeCount[4], StatusCodeCount[5], StatusCodeCount[0],
   TotalTransfered,
   HtmlTransfered,
   RequestsPerSecond,
   TimePerRequestConc,
   TimePerRequestMean,
   KbytesPerSecond);

   if (DoExercise) return;

   /********************/
   /* connection times */
   /********************/

   if (DebugStats) fprintf (stdout, "!(No.. && No..) ...\n");
   for (cnt = 0; cnt < ConnectionCount; cnt++)
   {
      sdptr = &StatDataPtr[cnt];

      if (!sdptr->ConnectBinTime[0] && !sdptr->ConnectBinTime[1]) continue;

      if (sdptr->ConnectBinTime[0] && !sdptr->EndBinTime[0])
      exit (SS$_BUGCHECK);
      if (!sdptr->ConnectBinTime[0])
         memcpy (&sdptr->ConnectBinTime, &sdptr->EndBinTime, 8);
      if (!sdptr->EndWriteBinTime[0])
         memcpy (&sdptr->EndWriteBinTime, &sdptr->EndBinTime, 8);
      if (!sdptr->BeginReadBinTime[0])
         memcpy (&sdptr->BeginReadBinTime, &sdptr->EndBinTime, 8);

      status = lib$sub_times (&sdptr->ConnectBinTime,
                              &sdptr->StartBinTime,
                              &ConnectBinTime);
      if (VMSnok (status)) exit (status);
      status = lib$sub_times (&sdptr->BeginReadBinTime,
                              &sdptr->EndWriteBinTime,
                              &WaitBinTime);
      if (VMSnok (status)) exit (status);
      status = lib$sub_times (&sdptr->EndBinTime,
                              &sdptr->ConnectBinTime,
                              &ProcessingBinTime);
      if (VMSnok (status)) exit (status);
      status = lib$sub_times (&sdptr->EndBinTime,
                              &sdptr->StartBinTime,
                              &TotalBinTime);
      if (VMSnok (status)) exit (status);

#ifdef __ia64
      /* lib$cvts_...() is not supported on Alpha V7.3-2 or earlier */
      status = lib$cvts_from_internal_time (&CvtTimeFloatSeconds,
                                            &sdptr->ConnectTime,
                                            &ConnectBinTime);
      if (VMSnok (status)) exit (status);
      status = lib$cvts_from_internal_time (&CvtTimeFloatSeconds,
                                            &sdptr->WaitTime,
                                            &WaitBinTime);
      if (VMSnok (status)) exit (status);
      status = lib$cvts_from_internal_time (&CvtTimeFloatSeconds,
                                            &sdptr->ProcessingTime,
                                            &ProcessingBinTime);
      if (VMSnok (status)) exit (status);
      status = lib$cvts_from_internal_time (&CvtTimeFloatSeconds,
                                            &sdptr->TotalTime,
                                            &TotalBinTime);
      if (VMSnok (status)) exit (status);
#else
      status = lib$cvtf_from_internal_time (&CvtTimeFloatSeconds,
                                            &sdptr->ConnectTime,
                                            &ConnectBinTime);
      if (VMSnok (status)) exit (status);
      status = lib$cvtf_from_internal_time (&CvtTimeFloatSeconds,
                                            &sdptr->WaitTime,
                                            &WaitBinTime);
      if (VMSnok (status)) exit (status);
      status = lib$cvtf_from_internal_time (&CvtTimeFloatSeconds,
                                            &sdptr->ProcessingTime,
                                            &ProcessingBinTime);
      if (VMSnok (status)) exit (status);
      status = lib$cvtf_from_internal_time (&CvtTimeFloatSeconds,
                                            &sdptr->TotalTime,
                                            &TotalBinTime);
      if (VMSnok (status)) exit (status);
#endif

      sdptr->ConnectTime *= 1000.0;
      sdptr->WaitTime *= 1000.0;
      sdptr->ProcessingTime *= 1000.0;
      sdptr->TotalTime *= 1000.0;

      ConnectTimeMin = MINOF (ConnectTimeMin, sdptr->ConnectTime);
      WaitTimeMin = MINOF (WaitTimeMin, sdptr->WaitTime);
      ProcessingTimeMin = MINOF (ProcessingTimeMin, sdptr->ProcessingTime);
      TotalTimeMin = MINOF (TotalTimeMin, sdptr->TotalTime);

      ConnectTimeMax = MAXOF (ConnectTimeMax, sdptr->ConnectTime);
      WaitTimeMax = MAXOF (WaitTimeMax, sdptr->WaitTime);
      ProcessingTimeMax = MAXOF (ProcessingTimeMax, sdptr->ProcessingTime);
      TotalTimeMax = MAXOF (TotalTimeMax, sdptr->TotalTime);

      ConnectTimeTotal += sdptr->ConnectTime;
      WaitTimeTotal += sdptr->WaitTime;
      ProcessingTimeTotal += sdptr->ProcessingTime;
      TotalTimeTotal += sdptr->TotalTime;
   }

   if (ConnectTimeMin == MIN_SENTINAL) ConnectTimeMin = 0.0;
   if (WaitTimeMin == MIN_SENTINAL) WaitTimeMin = 0.0;
   if (ProcessingTimeMin == MIN_SENTINAL) ProcessingTimeMin = 0.0;
   if (TotalTimeMin == MIN_SENTINAL) TotalTimeMin = 0.0;

   if (DebugStats) fprintf (stdout, "(float)ConnectionCount ...\n");
   ConnectTimeTotal /= (float)ConnectionCount;
   WaitTimeTotal /= (float)ConnectionCount;
   TotalTimeTotal /= (float)ConnectionCount;
   ProcessingTimeTotal /= (float)ConnectionCount;

   if (DebugStats) fprintf (stdout, "StatDataPtr ...\n");
   for (cnt = 0; cnt < ConnectionCount; cnt++)
   {
      float  fs;
      sdptr = &StatDataPtr[cnt];
      fs = sdptr->ConnectTime - ConnectTimeTotal;
      ConnectStdDev += fs * fs;
      fs = sdptr->WaitTime - WaitTimeTotal;
      WaitStdDev += fs * fs;
      fs = sdptr->ProcessingTime - ProcessingTimeTotal;
      ProcessingStdDev += fs * fs;
      fs = sdptr->TotalTime - TotalTimeTotal;
      TotalStdDev += fs * fs;
   }

   if (DebugStats) fprintf (stdout, "ConnectCount ...\n");
   if (ConnectionCount > 1)
   {
      ConnectStdDev = sqrt(ConnectStdDev / (ConnectionCount - 1));
      WaitStdDev = sqrt(WaitStdDev / (ConnectionCount - 1));
      ProcessingStdDev = sqrt(ProcessingStdDev / (ConnectionCount - 1));
      TotalStdDev = sqrt(TotalStdDev / (ConnectionCount - 1));
   }
   else
      ConnectStdDev = WaitStdDev = ProcessingStdDev = TotalStdDev = 0.0;

   if (DebugStats) fprintf (stdout, "Connect ...\n");
   qsort (StatDataPtr, ConnectionCount, sizeof(struct StatStruct),
          (int(*)(const void*, const void*))CompareConnect);
   if (DebugStats) DebugStatsData ();
   if ((ConnectionCount > 1) && (ConnectionCount % 2))
      ConnectTimeMedian =
         (StatDataPtr[ConnectionCount / 2].ConnectTime +
          StatDataPtr[ConnectionCount / 2 + 1].ConnectTime) / 2.0;
   else
      ConnectTimeMedian =
         StatDataPtr[ConnectionCount / 2].ConnectTime;
   if (DebugStats) fprintf (stdout, "%8.3f\n", ConnectTimeMedian);

   if (DebugStats) fprintf (stdout, "Response (wait) ...\n");
   qsort (StatDataPtr, ConnectionCount, sizeof(struct StatStruct),
          (int(*)(const void*, const void*))CompareResponse);
   if (DebugStats) DebugStatsData ();
   if ((ConnectionCount > 1) && (ConnectionCount % 2))
      WaitTimeMedian =
         (StatDataPtr[ConnectionCount / 2].WaitTime +
          StatDataPtr[ConnectionCount / 2 + 1].WaitTime) / 2.0;
   else
      WaitTimeMedian =
         StatDataPtr[ConnectionCount / 2].WaitTime;
   if (DebugStats) fprintf (stdout, "%8.3f\n", WaitTimeMedian);

   if (DebugStats) fprintf (stdout, "Processing ...\n");
   qsort (StatDataPtr, ConnectionCount, sizeof(struct StatStruct),
          (int(*)(const void*, const void*))CompareProcessing);
   if (DebugStats) DebugStatsData ();
   if ((ConnectionCount > 1) && (ConnectionCount % 2))
      ProcessingTimeMedian =
         (StatDataPtr[ConnectionCount / 2].ProcessingTime +
          StatDataPtr[ConnectionCount / 2 + 1].ProcessingTime) / 2.0;
   else
      ProcessingTimeMedian =
         StatDataPtr[ConnectionCount / 2].ProcessingTime;
   if (DebugStats) fprintf (stdout, "%8.3f\n", ProcessingTimeMedian);

   if (DebugStats) fprintf (stdout, "Total ...\n");
   qsort (StatDataPtr, ConnectionCount, sizeof(struct StatStruct),
          (int(*)(const void*, const void*))CompareTotal);
   if (DebugStats) DebugStatsData ();
   if ((ConnectionCount > 1) && (ConnectionCount % 2))
      TotalTimeMedian =
         (StatDataPtr[ConnectionCount / 2].TotalTime +
          StatDataPtr[ConnectionCount / 2 + 1].TotalTime) / 2.0;
   else
      TotalTimeMedian =
         StatDataPtr[ConnectionCount / 2].TotalTime;
   if (DebugStats) fprintf (stdout, "%8.3f\n", TotalTimeMedian);

   fprintf (stdout,
"\n\
Connection Times (mS)\n\
                 min     mean  [+/-sd]    median      max\n\
Connect:    %8.3f %8.3f %8.3f  %8.3f %8.3f\n\
Processing: %8.3f %8.3f %8.3f  %8.3f %8.3f\n\
Waiting:    %8.3f %8.3f %8.3f  %8.3f %8.3f\n\
Total:      %8.3f %8.3f %8.3f  %8.3f %8.3f\n",
   ConnectTimeMin, ConnectTimeTotal, ConnectStdDev,
      ConnectTimeMedian, ConnectTimeMax,
   ProcessingTimeMin, ProcessingTimeTotal, ProcessingStdDev,
      ProcessingTimeMedian, ProcessingTimeMax,
   WaitTimeMin, WaitTimeTotal, WaitStdDev,
      WaitTimeMedian, WaitTimeMax,
   TotalTimeMin, TotalTimeTotal, TotalStdDev,
      TotalTimeMedian, TotalTimeMax);

   if (!NoConfidence)
   {
#define SANE(what,avg,mean,sd) \
      { \
         double db = avg - mean; \
         if (db < 0) db = -db; \
         if (db > 2 * sd ) \
            fprintf (stdout, \
"ERROR: The median and mean for " what " are more than twice the\n\
       standard deviation apart. These results are NOT reliable.\n"); \
         else \
         if (db > sd ) \
            fprintf (stdout, \
"WARNING: The median and mean for " what " are not within a normal\n\
         deviation.  These results are propably not that reliable.\n"); \
      }
      SANE ("the initial connection time",
            ConnectTimeTotal, ConnectTimeMedian, ConnectStdDev);
      SANE ("the processing time",
            ProcessingTimeTotal, ProcessingTimeMedian, ProcessingStdDev);
      SANE ("the waiting time",
            WaitTimeTotal, WaitTimeMedian, WaitStdDev);
      SANE ("the total time",
            TotalTimeTotal, TotalTimeMedian, TotalStdDev);
#undef SANE
   }

   if (!NoPercentiles)
   {
      /* final sort was by total times */
      if (ConnectionCount > 1)
      {
         fprintf (stdout,
"\nPercentage of the requests served within a certain time (mS)\n\
  50%%  %8.3f\n\
  66%%  %8.3f\n\
  75%%  %8.3f\n\
  80%%  %8.3f\n\
  90%%  %8.3f\n\
  95%%  %8.3f\n\
  98%%  %8.3f\n\
  99%%  %8.3f\n\
 100%%  %8.3f\n",
         StatDataPtr[(int)((float)ConnectionCount * 0.50)].TotalTime,
         StatDataPtr[(int)((float)ConnectionCount * 0.66)].TotalTime,
         StatDataPtr[(int)((float)ConnectionCount * 0.75)].TotalTime,
         StatDataPtr[(int)((float)ConnectionCount * 0.80)].TotalTime,
         StatDataPtr[(int)((float)ConnectionCount * 0.90)].TotalTime,
         StatDataPtr[(int)((float)ConnectionCount * 0.95)].TotalTime,
         StatDataPtr[(int)((float)ConnectionCount * 0.98)].TotalTime,
         StatDataPtr[(int)((float)ConnectionCount * 0.99)].TotalTime,
         StatDataPtr[(int)((float)ConnectionCount - 1)].TotalTime);
      }
   }
}
 
/*****************************************************************************/
/*
Used by qsort() in ReportTotals().
*/

int CompareConnect
(
struct StatStruct *a,
struct StatStruct *b
)
{
   if ((a->ConnectTime) < (b->ConnectTime)) return (-1);
   if ((a->ConnectTime) > (b->ConnectTime)) return (1);
   return (0);
}

/*****************************************************************************/
/*
Used by qsort() in ReportTotals().
*/

int CompareResponse
(
struct StatStruct *a,
struct StatStruct *b
)
{
   if ((a->WaitTime) < (b->WaitTime)) return (-1);
   if ((a->WaitTime) > (b->WaitTime)) return (1);
   return (0);
}

/*****************************************************************************/
/*
Used by qsort() in ReportTotals().
*/

int CompareProcessing
(
struct StatStruct *a,
struct StatStruct *b
)
{
   if ((a->ProcessingTime) < (b->ProcessingTime)) return (-1);
   if ((a->ProcessingTime) > (b->ProcessingTime)) return (1);
   return (0);
}

/*****************************************************************************/
/*
Used by qsort() in ReportTotals().
*/

int CompareTotal
(
struct StatStruct *a,
struct StatStruct *b
)
{
   if ((a->TotalTime) < (b->TotalTime)) return (-1);
   if ((a->TotalTime) > (b->TotalTime)) return (1);
   return (0);
}

/*****************************************************************************/
/*
*/

void DebugStatsData ()

{
   int  idx;
   struct StatStruct  *sdptr;

   fprintf (stdout, "              Connect     Wait  Process    Total\n");
   for (idx = 0; idx < ConnectionCount; idx++)
   {
      sdptr = &StatDataPtr[idx];
      fprintf (stdout, "[%4d] %4d  %8.3f %8.3f %8.3f %8.3f  %d\n",
               idx , sdptr->ConnectionNumber,
               sdptr->ConnectTime, sdptr->WaitTime,
               sdptr->ProcessingTime, sdptr->TotalTime,
               sdptr->RxCount);
   }
}

/****************************************************************************/
/*
Read the file contents specified by 'Source' into memory, set the pointer at
'FileTextPtr' to the contents and the file size at 'FileSizePtr'.  Returns a
VMS status value that should be checked.
*/ 

int ReadFileIntoMemory
(
char *Source,
char **FileTextPtr,
int *FileSizePtr
)
{
   static int  BytesRemaining;

   int  status,
        Bytes,
        BufferCount,
        Length;
   char  *BufferPtr,
         *LinePtr;
   FILE  *FilePtr;
   stat_t  FstatBuffer;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "ReadFileIntoMemory() |%s|\n", Source);

   if (!(FilePtr = fopen (Source, "r", "shr=get", "shr=put")))
   {
      status = vaxc$errno;
      if (Debug) fprintf (stdout, "fopen() %%X%08.08X\n", status);
      return (status);
   }

   if (fstat (fileno(FilePtr), &FstatBuffer) < 0)
   {
      status = vaxc$errno;
      if (Debug) fprintf (stdout, "fstat() %%X%08.08X\n", status);
      fclose (FilePtr);
      return (status);
   }

   Bytes = FstatBuffer.st_size;
   if (Debug) fprintf (stdout, "%d bytes\n", Bytes);
   /* a little margin for error ;^) */
   Bytes += 32;

   BufferPtr = calloc (Bytes, 1);
   if (!BufferPtr)
   {
      status = vaxc$errno;
      fclose (FilePtr);
      return (status);
   }
   BytesRemaining = Bytes;
   LinePtr = BufferPtr;

   BufferCount = 0;
   while (Length = fread (LinePtr, 1, BytesRemaining, FilePtr))
   {
      /** if (Debug) fprintf (stdout, "|%s|\n", LinePtr); **/
      LinePtr += Length;
      BufferCount += Length;
      BytesRemaining -= Length;
   }
   fclose (FilePtr);

   /** if (Debug) fprintf (stdout, "%d |%s|\n", BufferCount, BufferPtr); **/

   if (FileTextPtr) *FileTextPtr = BufferPtr;
   if (FileSizePtr) *FileSizePtr = BufferCount;

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Get "command-line" parameters, whether from the command-line or from a
configuration symbol or logical containing the equivalent.  This function has
been customized for this utility to allow it to be called multiple times during
the one use.
*/

void GetParameters
(
int argc,
char *argv[]
)
{
   static char  CommandLine [256];
   static unsigned long  Flags = 0;

   int  acnt, status;
   unsigned short  Length;
   char  ch;
   char  *aptr, *cptr, *clptr, *sptr;
   $DESCRIPTOR (CommandLineDsc, CommandLine);

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "GetParameters() %d\n", argc);

   /**************/
   /* initialize */
   /**************/

   ReadBufferSize = DEFAULT_READ_BUFFER_SIZE;
   ServerPort = DEFAULT_SERVER_PORT;

   CliProxyServerHostPtr = SpecifiedPathPtr = "";
   
   if (argc <= 1) return;

   if (argv[1][0] == '-' || argv[1][0] == '+')
   {
      /*************/
      /* U**x-like */
      /*************/

      acnt = 1;
      while (acnt < argc)
      {
         if (Debug) fprintf (stdout, "%d |%s|\n", acnt, argv[acnt]);

         if (*(unsigned short*)argv[acnt] == '-c')
         {
            if (acnt++ < argc)
               CliConcurrency = atoi(argv[acnt]);
         }
         else
         if (*(unsigned short*)argv[acnt] == '-d')
            NoPercentiles = true;
         else
         if (*(unsigned short*)argv[acnt] == '-n')
         {
            if (acnt++ < argc)
               CliNumber = atoi(argv[acnt]);
         }
         else
         if (*(unsigned short*)argv[acnt] == '-h')
            DoShowHelp = true;
         else
         if (*(unsigned short*)argv[acnt] == '-i')
            DoHeadMethod = true;
         else
         if (*(unsigned short*)argv[acnt] == '-k')
            DoPersistentConnect = true;
         else
         if (*(unsigned short*)argv[acnt] == '-p')
         {
            if (acnt++ < argc)
               CliPostPtr = argv[acnt];
         }
         else
         if (*(unsigned short*)argv[acnt] == '-q')
            DoQuietly = true;
         else
         if (*(unsigned short*)argv[acnt] == '-S')
            NoConfidence = true;
         else
         if (*(unsigned short*)argv[acnt] == '-T')
         {
            if (acnt++ < argc)
               PostContentTypePtr = argv[acnt];
         }
         else
         if (*(unsigned short*)argv[acnt] == '-V')
            DoShowVersion = true;
         else
         if (argv[acnt][0] == '-')
         {
            fprintf (stdout, "%%%s-E-IVQUAL, unrecognized switch\n \\%s\\\n",
                     Utility, argv[acnt]);
            exit (STS$K_ERROR | STS$M_INHIB_MSG);
         }
         else
         if (!SpecifiedPathPtr[0])
            SpecifiedPathPtr = argv[acnt];
         else
         {
            fprintf (stdout, "%%%s-E-MAXPARM, too many parameters\n \\%s\\\n",
                     Utility, argv[acnt]);
            exit (STS$K_ERROR | STS$M_INHIB_MSG);
         }
         if (acnt >= argc)
         {
            fprintf (stdout, "%%%s-W-VALREQ, missing qualifier or keyword\n",
                     Utility);
            exit (STS$K_ERROR | STS$M_INHIB_MSG);
         }
         acnt++;
      }

      return;
   }

   /************/
   /* DCL-like */
   /************/

   /* get the entire command line following the verb */
   if (VMSnok (status =
       lib$get_foreign (&CommandLineDsc, 0, &Length, &Flags)))
      exit (status);
   (clptr = CommandLine)[Length] = '\0';
   if (Debug) fprintf (stdout, "clptr |%s|\n", clptr);

   aptr = NULL;
   ch = *clptr;
   for (;;)
   {
      if (aptr != NULL && *aptr == '/') *aptr = '\0';
      if (!ch) break;

      *clptr = ch;
      if (Debug) fprintf (stdout, "clptr |%s|\n", clptr);
      while (*clptr && isspace(*clptr)) *clptr++ = '\0';
      aptr = clptr;
      if (*clptr == '/') clptr++;
      while (*clptr && !isspace (*clptr) && *clptr != '/')
      {
         if (*clptr != '\"')
         {
            clptr++;
            continue;
         }
         cptr = clptr;
         clptr++;
         while (*clptr)
         {
            if (*clptr == '\"')
               if (*(clptr+1) == '\"')
                  clptr++;
               else
                  break;
            *cptr++ = *clptr++;
         }
         *cptr = '\0';
         if (*clptr) clptr++;
      }
      ch = *clptr;
      if (*clptr) *clptr = '\0';
      if (Debug) fprintf (stdout, "aptr |%s|\n", aptr);
      if (!*aptr) continue;

      /**************/
      /* parameters */
      /**************/

      if (strsame (aptr, "/BUFFER=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         ReadBufferSize = atoi(cptr);
         continue;
      }
      if (strsame (aptr, "/CHARSET=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         CliAcceptCharsetPtr = cptr;
         continue;
      }
      if (strsame (aptr, "/CONCURRENCY=", 5))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         CliConcurrency = atoi(cptr);
         continue;
      }
      if (strsame (aptr, "/CONTENT=", 5))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         PostContentTypePtr = cptr;
         continue;
      }
      if (strsame (aptr, "/DBUG", -1))
      {
         Debug = DebugStats = true;
         continue;
      }
      if (strsame (aptr, "/DBUGSTATS", -1))
      {
         DebugStats = true;
         continue;
      }
      if (strsame (aptr, "/DELAYS", 6))
      {
         NoNoDelaysAtAll = true;
         continue;
      }
      if (strsame (aptr, "/EAVESDROP", 4))
      {
         DoEavesdrop = true;
         continue;
      }
      if (strsame (aptr, "/ENCODING=", 5))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         PostContentEncodingPtr = cptr;
         continue;
      }
      if (strsame (aptr, "/EXERCISE", 4))
      {
         DoExercise = true;
         continue;
      }
      if (strsame (aptr, "/HEAD", 4))
      {
         DoHeadMethod = true;
         continue;
      }
      if (strsame (aptr, "/HELP", 4))
      {
         DoShowHelp = true;
         continue;
      }
      if (strsame (aptr, "/KEEPALIVE", 4))
      {
         DoPersistentConnect = true;
         continue;
      }
      if (strsame (aptr, "/LANGUAGE=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         CliAcceptLanguagePtr = cptr;
         continue;
      }
      if (strsame (aptr, "/NOCONFIDENCE", 6))
      {
         NoConfidence = true;
         continue;
      }
      if (strsame (aptr, "/NODELACK", 6))
      {
         NoDelayAck = true;
         continue;
      }
      if (strsame (aptr, "/NONAGLE", 6))
      {
         NoDelayNagle = true;
         continue;
      }
      if (strsame (aptr, "/NOHOST", 6))
      {
         NoHost = true;
         continue;
      }
      if (strsame (aptr, "/NOPERCENTILES", 6))
      {
         NoPercentiles = true;
         continue;
      }
      if (strsame (aptr, "/PERSISTENT", 4))
      {
         DoPersistentConnect = true;
         continue;
      }
      if (strsame (aptr, "/NUMBER=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         CliNumber = atoi(cptr);
         continue;
      }
      if (strsame (aptr, "/PIPELINE=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         CliPipeline = atoi(cptr);
         continue;
      }
      if (strsame (aptr, "/POST=", 4))
      {
         DoPostMethod = true;
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         CliPostPtr = cptr;
         continue;
      }
      if (strsame (aptr, "/PROTOCOL=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         if (strsame (cptr, "1.1", -1))
         {
            HttpProtocol = 11;
            HttpProtocolPtr = "HTTP/1.1";
         }
         else
         if (strsame (cptr, "1.0", -1))
         {
            HttpProtocol = 10;
            HttpProtocolPtr = "HTTP/1.0";
         }
         else
         {
            fprintf (stdout, "%%%s-E-IVKEYW, unrecognized keyword\n \\%s\\\n",
                     Utility, aptr+1);
            exit (STS$K_ERROR | STS$M_INHIB_MSG);
         }
         continue;
      }
      if (strsame (aptr, "/QUIET", 4))
      {
         DoQuietly = true;
         continue;
      }
      if (strsame (aptr, "/SEGMENT=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         PostSegmentSize = atoi(cptr);
         continue;
      }
      if (strsame (aptr, "/SILENT", 4))
      {
         DoSilently = true;
         continue;
      }
      if (strsame (aptr, "/TRANSFER=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         if (!strsame (cptr, "CHUNKED", 7))
         {
            fprintf (stdout, "%%%s-E-IVKEYW, unrecognized keyword\n \\%s\\\n",
                     Utility, aptr+1);
            exit (STS$K_ERROR | STS$M_INHIB_MSG);
         }
         DoTransferEncodingChunked = true;
         cptr += 7;
         if (*cptr == '=')
         {
            cptr++;
            TransferEncodingChunkedSize = atoi(cptr);
         }
         continue;
      }
      if (strsame (aptr, "/VERSION", 4))
      {
         DoShowVersion = true;
         continue;
      }

      if (*aptr == '/')
      {
         fprintf (stdout, "%%%s-E-IVQUAL, unrecognized qualifier\n \\%s\\\n",
                  Utility, aptr+1);
         exit (STS$K_ERROR | STS$M_INHIB_MSG);
      }

      if (!SpecifiedPathPtr[0])
      {
         SpecifiedPathPtr = aptr;
         continue;
      }

      fprintf (stdout, "%%%s-E-MAXPARM, too many parameters\n \\%s\\\n",
               Utility, aptr);
      exit (STS$K_ERROR | STS$M_INHIB_MSG);
   }
}

/*****************************************************************************/
/*
*/
 
void ShowHelp ()
 
{
   /*********/
   /* begin */
   /*********/

   fprintf (stdout,
"%%%s-I-HELP, usage for the WASDbench :^) utility (%s)\n\
\n\
WASDbench - an analogue to ApacheBench (AB).\n\
It has a significant performance advantage (looks like ~25%%) using $QIOs.\n\
WB supports a subset of AB-compatible switches.\n\
\n\
$ WB [qualifiers ...] URL\n\
$ WB [switches ...] URL\n\
\n\
/BUFFER=<integer> /CHARSET=<string> /CONCURRENCY=<integer> /CONTENT=<string>\n\
/DELAYS /EAVESDROP /ENCODING=<string> /EXERCISE /HEAD /KEEPALIVE\n\
/LANGUAGE=<string> /NUMBER=<integer> /NOCONFIDENCE /NODELACK /NONAGLE\n\
/NOPERCENTILES /PERSISTENT /PIPELINE=<integer> /PROTOCOL=[1.1|1.0]\n\
/POST=<parameter> /QUIET /SEGMENT=<integer> /SILENT\n\
/TRANSFER=CHUNKED[=<integer>] /VERSION\n\
\n\
Usage examples:\n\
  $ WB /CONCURRENT=10 /NUMBER=100 http://the.host.name/the/path\n\
  $ WB -c 10 -n 100 http://the.host.name/the/path\n\
  $ WB /POST=wasd_root:[exercise]64k.txt -c 5 -n 50 http://the.host.name/path/\n\
  $ WB -p \"/wasd_root/exercise/64k.txt\" -c 5 -n 50 http://the.host.name/path/\n\
\n",
   Utility, SOFTWAREID);
 
   exit (SS$_NORMAL);
}
 
/*****************************************************************************/
/*
*/

char* ErrorMessage (int VmsStatus)

{
   static char  Message [256];

   int  status;
   short int  Length;
   $DESCRIPTOR (MessageDsc, Message);

   /*********/
   /* begin */
   /*********/

   Message[0] = '\0';
   if (VmsStatus)
   {
      /* text component only */
      sys$getmsg (VmsStatus, &Length, &MessageDsc, 1, 0);
      Message[Length] = '\0';
   }
   if (Message[0])
      return (Message);
   else
      return ("(internal error)");
}
 
/****************************************************************************/
/*
Does a case-insensitive, character-by-character string compare and returns 
true if two strings are the same, or false if not.  If a maximum number of 
characters are specified only those will be compared, if the entire strings 
should be compared then specify the number of characters as 0.
*/ 
 
boolean strsame
(
char *sptr1,
char *sptr2,
int  count
)
{
   while (*sptr1 && *sptr2)
   {
      if (toupper (*sptr1++) != toupper (*sptr2++)) return (false);
      if (count)
         if (!--count) return (true);
   }
   if (*sptr1 || *sptr2)
      return (false);
   else
      return (true);
}
 
/****************************************************************************/

