/*****************************************************************************/ /* 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= qualifier provides the content for a set of POSTed requests. The 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= qualifier. The POSTed body is transfered in /SEGMENT= 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= 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= response read buffer size (none) /CHARSET= Accept-Charset string (none) /CONCURRENCY= number of simultaneous connections -C /CONTENT= content-type when POSTing (defaults to text/plain) -T /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= adds a "Content-Encoding: " 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= 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= total number of connections benchmarked -n /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= generate this many requests back-to-back /POST= POST the contents of this literal/file -p /PROTOCOL=[1.1|1.0] HTTP protocol (defaults to 1.1) (none) /QUIET minimise output -q /SEGMENT= POST segment size (amount per write) (none) /SILENT just make requests, no output (use when exercising) (none) /TRANSFER=CHUNKED[=] transfer encoding is chunked with optional size (none) /VERSION print version information -V BUILD DETAILS ------------- See BUILD_WB.COM COPYRIGHT --------- Copyright (C) 1996-2015 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 #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 #include #include #include #include #include #include #include #include /* VMS related header files */ #include #include #include #include #include #include #include #include #include #include /* Internet-related header files */ #include #include #include #include /* 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= /CHARSET= /CONCURRENCY= /CONTENT=\n\ /DELAYS /EAVESDROP /ENCODING= /EXERCISE /HEAD /KEEPALIVE\n\ /LANGUAGE= /NUMBER= /NOCONFIDENCE /NODELACK /NONAGLE\n\ /NOPERCENTILES /PERSISTENT /PIPELINE= /PROTOCOL=[1.1|1.0]\n\ /POST= /QUIET /SEGMENT= /SILENT\n\ /TRANSFER=CHUNKED[=] /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); } /****************************************************************************/