Thanks to Owen Garrett, who reminded me that I should have
mentioned a little more details about the client configuration.

My modified SURGE client fetch web pages on an "object" basis,
each object contains multiple pages. For each object the client
uses HTTP/1.1 keepalive, but not pipeline. After an object
being fetched completely, the client close the connection
to the server and reopen a new one for next object.

The delay time I added was between each web pages, so the
client goes to sleep for a little while with the connection
being still open.

FYI, I have attached the client code. Anyone have a wild guess
on what's going on? ;-) Thanks a lot!

-Min

On Mon, Feb 10, 2003 at 01:50:35PM -0600, Min Xu wrote:
> Hi All,
> 
> Sorry I am posting this directly to the development list. But
> I think this is not a user setup problem and it is so strange
> maybe only you guys will have a clue on what's going on.
> 
> I am a student of UW-Madison. In order to study computer
> architecture of commercial multiprocessor servers, we have used
> APACHE as one of our important workloads.
> 
> I am the one who setup the workload on a 14-processor Sun
> Enterprise server. During setup I found a very strange behavior
> of the apache server (running with worker MPM). Essentially the
> strange thing is that:
> 
>   The server optimal throughput is not achieved by using a
>   greedy client, who drive the server with no think time. But
>   with tiny amount of think time, much better throughput is
>   archievable. Also, with the greedy client, the server's
>   performance decreased over time, which seems to be very
>   counter-intuitive.
> 
> Of course, just give you the short decription above does not
> help you to help me. So I will give you the detail problem
> description and data in the following. With the understanding
> of the source code, maybe you can give me some more hypothesises
> to try on.
> 
> Workload background
> -------------------
> The setup of apache workload on is fairly simple comparing with
> some of the other workloads we have (OLTP). In this workload, we
> have a HTTP server and an automatic request generator(SURGE).
> Both of the programs are highly multi-threaded. The server has
> a pool of static text files to be served from a known URL to the
> request generator (the client). The size of the files follows a
> statistical distribution. And the client has multiple threads each
> emulate a user who access a serial of files in fixed order.
> 
> In previous setup of the workload, we have removed client think time.
> The basis of that is the following: (we also have to put the server
> and the client on the same machine for other reasons)
> 
> The workload (server + client) is a closed queueing system. The
> throughput of the system is ultimately determined by the bottleneck in
> the system. Having think time in the client only increase the parallelism
> in the system. It shouldn't change the maximum throughput too much.
> BTW, our goal is to archieve realistic workload setup with available
> hardware.
> 
> If you think about it, for our current server throughput level, say 5000
> trans/sec, if each user have 1 second think time between fetching each
> file, this will need 5000 users to sustain this throughput. On the other
> hand, if we remove the think time from the client, maybe 10 users can also
> generate the same 5000 requests per second. So the difference here is that
> one server has 5000 httpd threads and the other has only 10 httpd threads.
> 10 won't be worse(in terms of server behavior) than 5000, right? Greedy
> client won't be worse(in terms of performance) than the lazy client, right?
> 
> Well it is not that simple...
> 
> 
> I know how to get higher performance, but I don't know why it works!
> --------------------------------------------------------------------
> I have two version of surge clients in my hand. One is the original,
> one is my modified version. The difference between them would be the
> client efficiency. My modified version would fetch files more efficiently
> (because I made it emulate a simpler user) and have less thread
> switching overhead.
> 
> However, when I comparing the server throughput using these two clients,
> I got very surprising results, roughly:
> 
>   old client: 3000 trans/sec
>   new client: starts out from 4700 trans/sec, gradually degrade to 2500
>               trans/sec after 10-20 minutes of runtime.
> 
> And this really puzzled me for a long time. My supposedly performance
> enhancement did not improve the server Xput, but hurt it!
> 
> Turns out the reason for this is the new client was too efficient! I
> added the think time between each URL request and new client was able
> to drive the server Xput to as high as 5000 trans/sec. But note, the
> real interesting thing is not the think time, but how sensitive the
> Xput was affected by it.
> 
> I'd prefer to call the think time "delay time" in the following because
> I really only introduced very small amount of delay between each file
> fetch. The result can be seen in the following plots:
> 
> http://www.cs.wisc.edu/~xu/files/delay_results.eps
> http://www.cs.wisc.edu/~xu/files/side1.eps
> http://www.cs.wisc.edu/~xu/files/side2.eps
> 
> In this experiment, instead of using both old and new version of the
> client, I just used the new version with varying delay time and number
> of threads. Since there are two dimensions of freedom in the client,
> the plot is in 3D. The figures side1 and side2 is roughly the 2D
> projection of the Xput vs. thread and Xput vs. delay time.
> 
> Each point on the plot is a 30 minutes benchmarking on a 14P MP system.
> 
> Clearly, driving the server using no delay time is not optimal. No
> matter using same amount of threads or less number of threads, the
> server Xput is no higher than delayed counterparts. However, you can see,
> the server Xput raise rapidly with client number when delay time is 0.
> On the other hand, with small number clients, server Xput is reverse
> proportional to the the delay time. And with larger clients number,
> server Xput is proportional to delay time.
> 
> I don't understand why small(1-3us, with nanosleep on of solaris) delay
> time would help?
> 
> Some hypothesises are that apache server itself have some internals to
> slowdown greedy clients. Or Solaris did not schedule the server threads
> well enough to handle short request interval. Or, the greedy client
> consumed too much cpu time?
> 
> I'd appreciate any suggestions/comments from you.
> 
> -Min
> 
> -- 
> Rapid keystrokes and painless deletions often leave a writer satisfied with
> work that is merely competent.
>   -- "Writing Well" Donald Hall and Sven Birkerts

-- 
Rapid keystrokes and painless deletions often leave a writer satisfied with
work that is merely competent.
  -- "Writing Well" Donald Hall and Sven Birkerts
/****************************************************************************/
/*                  Copyright 1997, Trustees of Boston University.          */
/*                               All Rights Reserved.                       */
/*                                                                          */
/* Permission to use, copy, or modify this software and its documentation   */
/* for educational and research purposes only and without fee is hereby     */
/* granted, provided that this copyright notice appear on all copies and    */
/* supporting documentation.  For any other uses of this software, in       */
/* original or modified form, including but not limited to distribution in  */
/* whole or in part, specific prior permission must be obtained from Boston */
/* University.  These programs shall not be used, rewritten, or adapted as  */
/* the basis of a commercial software or hardware product without first     */
/* obtaining appropriate licenses from Boston University.  Boston University */
/* and the author(s) make no representations about the suitability of this  */
/* software for any purpose.  It is provided "as is" without express or     */
/* implied warranty.                                                        */
/*                                                                          */
/****************************************************************************/
/*                                                                          */
/*  Author:    Paul Barford                                                 */
/*             modified by Min Xu                                           */
/*  Title:     Client11s - HTTP/1.1 compliant Client thread with single     */
/*             pthread.                                                     */
/*  Revision:  1.0         5/7/98                                           */
/*                                                                          */
/*  This is the part of the SURGE code which actually makes document        */
/*  requests.  It requires Pthreads be installed in order to operate.       */
/*  See the SURGE-HOW-TO in order to compile and run properly.              */
/*                                                                          */
/*  Surge is set up so that you can multiple Surge client processes can be  */
/*  run on a single system.  This code used to be part of Surgeclient.c but */
/*  has been separated so that multiple HTTP flavors can be supported.  This */
/*  is the code that supports the HTTP/1.1 flavor.  This code is meant to   */
/*  be compiled as a module for Surgeclient.c.  The two routine contained   */
/*  here are what actually make the requests for files from the server.     */
/*  The ClientThread routine is what is referred to as a user-entity.  The  */
/*  ReaderThread routine is what actually makes a single document request.  */
/*                                                                          */
/*  In order to support HTTP/1.1 this code supports multiple persistent     */
/*  connections.  It supports both content length and chunked encoding to   */
/*  determine file end.  It should be used with fewer threads per process   */
/*  since it will use up to MAXCONCTS per user entity for file transfers.   */
/*                                                                          */
/*  This code is compiled in the Makefile as client11.c and compiles into   */
/*  client.o.  It's HTTP/1.0 counterpart is called client10.c               */
/*                                                                          */
/*             THESE ARE THE HTTP/1.1 COMPLIANT CLIENTTHREAD ROUTINES       */
/*             THAT INCLUDE THE ABILITY TO DO PIPELINED REQUESTS AND        */
/*             PIPELINE READING.                                            */
/*                                                                          */
/****************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/times.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <assert.h>
#include "Surgeclient.h"
#include "client11s.h"
#include "magic_call.h"

void *ClientThread (void *);
void ReaderRead (void *);
int Checkconnect (void *);
void Writerequest (int, void *);

/* Each of the following routines and variables are defined in Surgeclient.c */
/* The variables in caps are #defined in Surgeclient.h                      */
extern int systemno;
extern long object[MATCHSIZE][OBJLIMIT];
extern long seq[NAMESIZE];
extern char httpstart[URLSIZE];
extern char httpend[URLSIZE];

extern int connectsrvr ();
extern void loadoutput (long, long, long, int, long, long, long, long);
extern int fillreadbuff (int, char *, size_t);

extern unsigned long numobjects;
extern unsigned long seq_counter;
extern unsigned long out_counter;
extern unsigned long cnt_counter;
extern unsigned long long *tpmc_counter;
extern long *cnt;
extern unsigned int threadnum;
extern unsigned int thinktime;

static struct timeval system_starttime;
static struct timeval system_endtime;
static struct timezone system_tz;

static unsigned long long last_sum = 0;
static unsigned long long last_reported_sum = 0;
static double last_sec = 0;

static void end_a_transaction(int thread_id) {
  int i,j;
  unsigned long long sum = 0;
  double sec = 0;

  tpmc_counter[thread_id] += 1;

  if(thread_id == 0) {
    for(i = 0 ; i < threadnum ; i++) {
      sum += tpmc_counter[i];
    }

    /* report throughput */
    /*
    sum = tpmc_counter[thread_id];
    if(sum % 1000 == 0) {
    */
    /* make sure we don't call gettimeofday() too often */
    if(sum - last_sum > 5000) {
      gettimeofday (&system_endtime, &system_tz);
      /*
      printf("start %d:%d\n", system_starttime.tv_sec, system_starttime.tv_usec);
      printf("end %d:%d\n", system_endtime.tv_sec, system_endtime.tv_usec);
      */
      sec = system_endtime.tv_sec - system_starttime.tv_sec;
      sec += (system_endtime.tv_usec - system_starttime.tv_usec)/1000000;
      /*
      printf("trans: %d\n", sum);
      printf("secs: %f\n", sec);
      */
      if (sec - last_sec > 60) {
        printf("Client [%d] : %f sys_avg_xput %f sys_int_xput\n",
               thread_id, sum/sec, (sum-last_reported_sum)/(sec-last_sec));
        last_sec = sec;
        last_reported_sum = sum;

        /* crudely exit if runs for too long */
        if(HOUR && sec > (HOUR*3600)) {
          printf("Execution time is up!\n");
          exit(0);
        }
      }
      last_sum = sum;
    }
  }

  end_transaction_magic_call ();
}

/****************************************************************************/
/* This routine calls for the connection set up and calls the document      */
/* retriever routine as a separate thread.  It is the first routine which   */
/* is executed by the threads and is called from main.                      */
/****************************************************************************/

void *
ClientThread (void *threadno) {
  int numobjs, id, numfiles;
  long i, j, objindx, count;
  int debugflag = 0;
  struct readerdata reader;

  /* Begin by initializing all of the variables used in the ClientThread    */
  id = *(int *) threadno; /* assign local thread id */
  count = id; /* count is used to store the object index from OBJFIFO  */
  reader.conctno = -1;

  /* multifacet: to be safe */
  if(id == 0) {
    gettimeofday (&system_starttime, &system_tz);
  }

  /* multifacet: get client start time after the last thread started */
  if(id == threadnum-1) {
    int i;
    /* mark start of all the stats */
    gettimeofday (&system_starttime, &system_tz);
    for(i = 0 ; i < threadnum ; i++) {
      tpmc_counter[i] = 0;
    }
    last_sum = 0;
    last_reported_sum = 0;
    last_sec = 0;
  }

  /* Loop until all of the objects in name.txt (OBJFIFO) have been requested */
  while (1) {
    int stopfileloop = 0;

    /* multifacet specific:
     *   statically partition the request sequence amoung all threads
     */
    count += threadnum;
    if (count >= numobjects) {
      /* to end */
      break;
    } else {
      objindx = seq[count];
    }

    /*
    if(count%1000 == 0) {
      printf("SURGEclient %d, thread %d:  Object Count %ld\n",systemno, id, count);
    }
    */

    /* For HTTP1.1 - Get the number of objects to read before closing the */
    /* connections and startiang a new set of connections to server.      */
    /* I call this the "open connection" period.                          */

    numobjs = 0; /* for HTTP1.1 number of objects to get before new concts */
    if (numobjs == 0) {
      /* multifacet specific */
      numobjs = cnt[count];
      if (debugflag) {
        printf ("SURGEclient %d: Objs in open connect period = %d\n",
                systemno, numobjs);
      }
    }

    /* Get the file names associated with the selected object and request */
    /* them from the server. If HTTP1.1 then use multiple connections     */

    /* For pipelined HTTP/1.1 requests, find out how many files are   */
    /* in the current object and then split these between the         */
    /* connections                                                    */

    /* find out how many files */
    numfiles = 0; /* Number of files in current object                */
    while (stopfileloop == 0) {
      numfiles++;
      if (object[objindx][numfiles] == -1 || numfiles == OBJLIMIT) {
        stopfileloop = 1;
      }
    }

    /* xu note:
     *   get all files using one connection;
     *   we don't use http pipeline here because Surge client is buggy in
     *   parsing the pipeline output, which resulting in closing connections
     *   unnecessarily. Also, being on the same machine, the latency between
     *   client and server do not make sense to use http pipeline.
     */

    if (numfiles > 0) {
      static struct timespec rqtp;
      rqtp.tv_sec = thinktime / 1000000;
      rqtp.tv_nsec = (thinktime % 1000000) * 1000;

      /* Be sure server didn't close connection */
      while (reader.conctno == -1) {
        reader.conctno = connectsrvr ();
      }

      for (i = 0; i < numfiles; i++) {
        reader.threadno = id;
        reader.objectno = objindx;
        reader.nofiles = 1;
        reader.fileno[0] = i;
        ReaderRead(&reader);
        if( nanosleep(&rqtp, NULL) == -1) { /* sleep for a while */
          perror("nanosleep");
        }
      }

    } /* End if numfiles > 0 */

    /* For HTTP 1.1:  If objects for current "open connection" period     */
    /* have been transferred, then close all of open connections.  numobjs */
    /* tracks the number of objects for "open connection" period.  If all */
    /* objects have not been selected then be sure that connections are   */
    /* still open - if not, reopen them.                                  */

    if (numobjs == 1) {
      /* If all objects xfered, close connects */
      if (reader.conctno >= 0) {
        if ((close (reader.conctno)) < 0) {
          printf ("SURGEclient %d: Client %d Error closing connection %ld\n",
                  systemno, id, 0);
        }
        reader.conctno = -1;
        if (debugflag) {
          printf ("SURGEclient %d: Client %d Closed connection %ld\n",
                  systemno, id, 0);
        }
      }
      numobjs = 0;
    } else {
      /* If all objects not xfered, be sure all connects still open */
      Checkconnect (&reader);
      numobjs--; /* decrement # objects in this "open connection" period */
    }
  } /* End object select while loop */

  /* close all connections */
  if (reader.conctno != -1) {
    close(reader.conctno);
  }

  pthread_exit (0);

}       /* End ClientThread */

/****************************************************************************/
/* This routine is where the action takes place.  It sets up the HTTP       */
/* command, issues the command, reads the data from the open connection and */
/* takes a time stamp when the transaction finishes and then loads the      */
/* results array.  For pipelined reads and writes:  request the base file   */
/* and then read it.  Then request all of the remaining files and read them */
/****************************************************************************/

static char g_contlength[MAXTHREAD][20];
static char g_headbuff[MAXTHREAD][300];
static char g_rbuff[MAXTHREAD][READBUFFSZ];
void
ReaderRead (void *readdat) {
  int i, c, thrdid, numfiles, filesread, stopflg, conctstat, conctid;
  long objindx, fileindx[OBJLIMIT], length = 0;
  char *contlength = NULL;
  char *headbuff = NULL;
  char *rbuff = NULL;
  char parseline1[] = "chunked";
  char parseline2[] = "Content-Length";
  char parseline3[] = "\r\n\r\n";
  char parseline4[] = "HTTP";
  char *lineptr = NULL;
  char *headptr = NULL;
  int debugflag = 0;

  /* Each time this function is called by the ClientThread it */
  /* is responsible for requesting and reading one object from the */
  /* server. Begin by loading the object data from the readdat structure */

  thrdid = ((struct readerdata *) readdat)->threadno;
  objindx = ((struct readerdata *) readdat)->objectno;
  numfiles = ((struct readerdata *) readdat)->nofiles;
  Checkconnect(readdat);
  conctid = ((struct readerdata *) readdat)->conctno;

  rbuff = g_rbuff[thrdid];
  headbuff = g_headbuff[thrdid];
  contlength = g_contlength[thrdid];
  bzero(rbuff, READBUFFSZ);
  bzero(headbuff, 300);
  bzero(contlength, 20);

  for (i = 0; i < numfiles; i++) {
    fileindx[i] = ((struct readerdata *) readdat)->fileno[i];
    if (debugflag) {
      printf ("SURGEclient %d: Thread %d Object %ld Fileno %ld\n",
              systemno, thrdid, objindx, object[objindx][fileindx[i]]);
    }
  }

  /* Issue HTTP GET call for the specified file to the specified          */
  /* In the case of pipelined HTTP/1.1, request all files at once.        */

  filesread = 0;
  Writerequest (filesread, readdat);

  /* Read the file from the connection.  For HTTP1.1 chunked data format  */
  /* is supported.  So, read chunks until the "0 CRLF {footer CRLF} CRLF" */
  /* is seen and then consider the transfer to be complete.               */
  /* For HTTP 1.1 must be able to read data from persistent concts        */
  /* Read data from connection into the rbuff and then parse to see if    */
  /* chunked encoding is being used - if yes then read until the "0 CRLF  */
  /* {footer CRLF} CRLF" is seen otherwise use the content length field   */
  /* and read that number of bytes of message and then end.  By rfc2068   */
  /* "Messages MUST NOT include both a Content-Length header field and    */
  /* the "chunked" transfer coding.  If both are received, the            */
  /* Content-Length MUST be ignored."                                     */

  /* Start out by reading the first rbuff of data from the connection */

  if(conctid == -1) { printf("Bad connection before fetch file\n"); };
  c = fillreadbuff (conctid, rbuff, READBUFFSZ);
  if(c == -1) { printf("Bad read of the first file\n"); };
  if (debugflag) {
    printf ("Read first buffer of data size = %d\n", c);
  }

  /* Find the start of first message and place lineprt where data starts  */
  if ((lineptr = strstr (rbuff, parseline3)) == NULL) {
    /* cannot find message header */
    printf (" Can't find first message start - aborting object transfer\n");
    if (debugflag) {
      printf("c=%d", c);
      for(i=0; i<1000; i++) {
        printf("%d", rbuff[i]);
      }
      for(i=0; i<1000; i++) {
        printf("%c", rbuff[i]);
      }
      printf("rbuff=%s\n", rbuff);
      assert(0);
      printf ("Surgeclient %d: Thread %d Object %ld Fileno %ld",
              systemno, thrdid, objindx, object[objindx][fileindx[filesread]]);
    }
    filesread = numfiles;
    if (close (conctid) < 0) {
      printf ("SURGEclient %d: Thrd %d Error closing connect %d\n",
              systemno, thrdid, conctid);
    }
    ((struct readerdata *) readdat)->conctno = -1;
  } else {
    /* Store header for pipelinig */
    lineptr += 4; /* Place pointer after the CRLF CRLF in msg header */
    if (!(300 > lineptr - rbuff)) { printf("Must increase headbuff size!\n"); };
    strncpy (headbuff, rbuff, lineptr - rbuff);
  }

  /* For pipelined HTTP, read all of the files requested. Check after   */
  /* each file read that the connection is still opened. If not, reopen */
  /* and resend requests for the remaining files.                       */

  while (filesread < numfiles) {

    /* Parse the header from first data pkt if chunked encoding is used   */

    if (strstr (headbuff, parseline1) != NULL) {
      /* multifacet: apache don't use chucked encoding */
      printf("Strange! Our workload should not use chucked encoding!\n");

      if (debugflag) {
        printf ("Surgeclient %d: Thread %d Object %ld Fileno %ld", systemno,
                thrdid, objindx, object[objindx][fileindx[filesread]]);
        printf (" Chunked data transfer\n");
      }

      /* Look for the end string in each packet - begin at body start of  */
      /* first packet and check the length of the next chunk.  Then look  */
      /* for end string in subsequent chunks until the end of the packet  */
      /* Get additional packets of data when necessary until end string   */
      /* is found.                                                        */

      stopflg = 0;
      while (stopflg == 0 && c != -1) { /* Cycle through chunks until EOF */
        if (length + lineptr - rbuff < c - 1) {
          lineptr += length;    /* Set lineptr to 1st char of next chnk  */
          i = 0;
          bzero (contlength, 20);
          while (rbuff[i + lineptr - rbuff] != '\n') { /* Read chunk size */
            contlength[i] = rbuff[i + lineptr - rbuff];
            i++;
            /*  If size is on array boundry get next packet of data       */
            if (i + lineptr - rbuff == READBUFFSZ) {
              c = fillreadbuff (conctid, rbuff, READBUFFSZ);
              i = 0;
              if (debugflag > 1) {
                printf ("Read1 chunk buffer of data size = %d\n", c);
              }
            }
          }  /* End while of reading next chunk size */
          length = strtol (contlength, NULL, 16); /* Chunk size is hex   */
          if (debugflag > 1) {
            printf ("Next chunk size = %ld hex %s\n", length, contlength);
          }
          lineptr += 3 + i; /* put lineptr at start of data of next chunk */
          if (length == 0) { /* Look for CRLF CRLF string to end data. If */
            lineptr -= 3 + i; /* its not in this rbuff the get one more.  */
            if ((lineptr = strstr (lineptr, parseline3)) == NULL) {
              c = fillreadbuff (conctid, rbuff, READBUFFSZ);
              lineptr = rbuff; /* put lineptr at start of rbuff */
              if (debugflag > 1) {
                printf ("Read2 chunk buffer of data size = %d\n", c);
              }
            }
            if (c != -1) { /* Write out results if reading was successful */
              /*  Get the output sequence # and write results structure   */
              end_a_transaction(thrdid);
              if (debugflag) {
                printf ("Surgeclient %d: Thread %d Object %ld",
                        systemno, thrdid, objindx);
                printf (" Finished reading file %ld\n",
                        object[objindx][fileindx[filesread]]);
              }

              /* Now get the header for the next file which can lay       */
              /* across a buffer boundry so be careful in constructing    */
              /* headbuff and place the lineptr at the start of the new   */
              /* data.  NOTE:  PIPELINE CHUNKS HAVE NOT BEEN TESTED!!!!   */

              if (filesread + 1 < numfiles) {
                bzero (headbuff, 300);
                headptr = lineptr;  /* put headptr at start of next header */
                if (lineptr >= rbuff + c) { /*Get new pkt if headptr at end */
                  /* Before we can get a new packet, we much check to     */
                  /* see if the server has closed the connection.  If     */
                  /* so, reopen it and rerequest the remaining files      */

                  if ((conctstat = Checkconnect (readdat)) == 1) {
                    Writerequest (filesread, readdat);
                    conctid = ((struct readerdata *) readdat)->conctno;
                  }
                  c = fillreadbuff (conctid, rbuff, READBUFFSZ);
                  headptr = rbuff;
                  if (debugflag) {
                    printf ("Header is in next packet\n");
                    printf ("Read3 chunk buffer of data size = %d\n", c);
                  }
                } /* End if headptr at end of rbuff */
                lineptr = headptr;
                if ((lineptr = strstr (lineptr, parseline3)) != NULL) {
                  strncpy (headbuff, headptr, lineptr + 4 - headptr);
                  lineptr += 4; /* put lineptr at start of data      */
                  if (debugflag) {
                    printf ("Header in Page\n");
                  }
                } else { /* Header has gone over rbuff boundry          */
                  i = c - (headptr - rbuff);
                  if (i > 300) i = 300; /* Be sure to keep the index within */
                  if (i < 0) i = 0;     /* the length of headbuff          */
                  strncpy (headbuff, headptr, i); /* Copy start of header  */
                  c = fillreadbuff (conctid, rbuff, READBUFFSZ);
                  strncat (headbuff, rbuff, 300 - i); /* add data to headbuff */
                  if ((headptr = strstr (headbuff, parseline4)) != NULL) {
                    if ((headptr = strstr (headptr, parseline3)) != NULL) {
                      if ((lineptr = strstr (rbuff, parseline3)) != NULL) {
                        if (debugflag) {
                          printf ("Header over Boundary\n");
                        }
                        lineptr += 4;
                      } else {
                        c = -1;
                        if (debugflag) {
                          printf ("Error finding data start\n");
                        }
                      }
                    } else {
                      c = -1;
                      if (debugflag) {
                        printf ("Error finding next headr end\n");
                      }
                    }
                  } else {
                    c = -1;
                    if (debugflag) {
                      printf ("Error finding next headr start\n");
                    }
                  }
                } /* End else if header has gone over rbuff boundry      */
              } /* End if filesread < numfiles */
            } /* End if c != -1 to write results */
            stopflg = 1;        /* Chunk data end string found so end read  */
          }     /* End if length == 0 */
        } else { /* If chunk size is beyond current rbuff get next pkt  */
          length -= c - (lineptr - rbuff);
          c = fillreadbuff (conctid, rbuff, READBUFFSZ);
          lineptr = rbuff;
          if (debugflag) {
            printf ("Read chunk 4 buffer of data size = %d\n", c);
          }
        }
      } /* End while of cycle through chunks */
    } else if ((headptr = strstr (headbuff, parseline2)) != NULL) {

      /********************************************************************/
      /* content length is used to decide when to end                     */
      /********************************************************************/

      i = 0; /* A really ugly way to parse out "Content-Length: "  */
      bzero (contlength, 20);
      while (headbuff[i + headptr + 16 - headbuff] >= '0' &&
             headbuff[i + headptr + 16 - headbuff] <= '9' ) {
        contlength[i] = headbuff[i + headptr + 16 - headbuff];
        i++;
      }
      if (!(20 > i)) { printf("Must increase contlength buffer size!\n"); };
      length = strtol (contlength, NULL, 10); /* Content length is decimal */
      if (!(length > 0)) { printf("File length < 0??\n"); };
      if (debugflag) {
        printf ("Surgeclient %d: Thread %d Object %ld Fileno %ld", systemno,
                thrdid, objindx, object[objindx][fileindx[filesread]]);
        printf (" Content length used length = %ld\n", length);
      }

      /*   Test to see if end of message is in current rbuff of data    */
      /* xu note: length does not include the header! */
      length -= c - (lineptr - rbuff);
      if (debugflag > 1) {
        printf ("Data remaining after this packet = %ld\n", length);
      }

      /*   If data still remains to be read, read until length = 0      */
      while (length > 0 && c >= 0) {
        c = fillreadbuff (conctid, rbuff, READBUFFSZ);
        if (debugflag > 1) {
          printf ("Length = %ld Value of next read is %d\n", length, c);
        }
        length -= c;
        if (debugflag > 1) {
          printf ("Read Cont 1 buffer of data size = %d\n", c);
        }
      }
      if (c != -1) {
        /* Get the output sequence # and write results structure   */
        end_a_transaction(thrdid);
        if (debugflag) {
          printf ("Surgeclient %d: Thread %d Object %ld",
                   systemno, thrdid, objindx);
          printf (" Finished reading file %ld\n",
                  object[objindx][fileindx[filesread]]);
        }

        /* Now get the header for the next file which can lay           */
        /* across a buffer boundry so be careful in constructing        */
        /* headbuff and place the lineptr at the start of the new       */
        /* data.                                                        */

        if (filesread + 1 < numfiles) {
          if(!(length <= 0)) { printf("Length should be negtive\n"); };
          bzero (headbuff, 300);
          /* Put headptr at start of next (length < 0) */
          headptr = rbuff + c + length;
          if (headptr == rbuff + c) { /* Get new pkt if ptr at end of buff */

            /* Before we can get a new packet, we must check to see if  */
            /* the server has closed the connection.  If so, reopen it  */
            /* and rerequest the remaining files.                       */

            if ((conctstat = Checkconnect (readdat)) == 1) {
              Writerequest (filesread, readdat);
              conctid = ((struct readerdata *) readdat)->conctno;
            }
            c = fillreadbuff (conctid, rbuff, READBUFFSZ);
            if (c == -1) { printf("Error reading buffer contains next header\n"); };
            headptr = rbuff; /* next packet the get it */
            if (debugflag) {
              printf ("Header is in next packet\n");
            }
          } /* End if headptr at end of rbuff */

          if ((lineptr = strstr (headptr, parseline3)) != NULL) {
            lineptr += 4; /* put lineptr at start of data */
            if (!(300 > lineptr - headptr)) { printf("Need bigger headbuff\n"); };
            strncpy (headbuff, headptr, lineptr - headptr);
            if (debugflag) {
              printf ("Header in Page\n");
            }
          } else { /* Header has gone over rbuff boundry              */
            i = c - (headptr - rbuff);
            if (i > 299) i = 299; /* Be sure to keep the index with in   */
            if (i < 0) i = 0; /* the length of headbuff              */
            strncpy (headbuff, headptr, i); /* Copy start of header      */
            c = fillreadbuff (conctid, rbuff, READBUFFSZ);
            if (c == -1) { printf("Bad read of next header buffer\n"); };
            strncat (headbuff, rbuff, 299 - i); /* add data to headbuff  */
            if ((headptr = strstr (headbuff, parseline3)) != NULL) {
              if ((lineptr = strstr (rbuff, parseline3)) != NULL) {
                if (debugflag) {
                  printf ("Header over rbuff Boundary\n");
                }
                lineptr += 4;
              } else {
                if (debugflag) {
                  for(i = 0 ; i<READBUFFSZ; i++) {
                    printf("%c", rbuff[i]);
                    if(i>=10 && rbuff[i-10] == 0) break;
                  }
                  printf("\n");
                  for(i = 0 ; i<READBUFFSZ; i++) {
                    printf("%d", rbuff[i]);
                    if(i>=10 && rbuff[i-10] == 0) break;
                  }
                  printf("c=%d", c); printf("\nheadbuff=%s", headbuff); 
printf("\nrbuff=%s", rbuff); fflush(stdout); assert(0);
                }
                printf ("Error finding data start\n");
                c = -1;
              }
            } else {
              printf ("Error finding next header\n");
              c = -1;
              if (debugflag) {
                printf("c=%d", c); printf("\nheadbuff=%s", headbuff); 
printf("\nrbuff=%s", rbuff); fflush(stdout); assert(0);
              }
            }
          }
        } else { /* End if filesread + 1 < numfiles */
          if(!(length == 0)) {
            printf("Trailing data\n");
            c = -1;
          }
        }
      } else { /* End if c != -1 to write results */
        printf("Error during fetch rest of the file!\n");
      }
    } else {
      if (debugflag) {
        printf ("SURGEclient %d: Thread %d Objct %ld Fileno %ld", systemno,
                thrdid, objindx, object[objindx][fileindx[filesread]]);
        printf("c=%d", c); printf("\nheadbuff=%s", headbuff); printf("\nrbuff=%s", 
rbuff); fflush(stdout); assert(0);
      }
      printf ("  Can't find Chunk Size or Content Length - abort obj\n");
      c = -1;
    } /* End of length encoding selection */

    /* If the last file was successfully read, increment the filesread    */
    /* counter and reset the start times to the last end time.  If c = -1 */
    /* then there was a real problem so close connect and rereques the    */
    /* remaining files.                                                   */
  
    if (c != -1) { /* If c != -1 then the file was successfully read     */
      filesread++; /* Increment the # of files read from pipe            */
    } else {
      if (debugflag) {
        printf ("SURGEclient %d: Thread %d Objct %ld Fileno %ld", systemno,
                thrdid, objindx, object[objindx][fileindx[filesread]]);
      }
      filesread = numfiles;
      if (close (conctid) < 0) {
        printf ("SURGEclient %d: Thrd %d Error closing connect %d\n", systemno,
                thrdid, conctid);
      }
      ((struct readerdata *) readdat)->conctno = -1;
    }
  } /* End while filesread < numfiles for pipelined reading of an object  */

} /* End ReaderRead */

/****************************************************************************/
/* This routine checks the TCP connection for the specified reader.  If the */
/* connection is closed, it reopens it.  It returns a 0 if it did not have  */
/* to reopen the connection, it returns a 1 if it did have to reopen.       */
/* The inputs to this routine are the client thread number and the reader   */
/* number which specify which connection to check.                          */
/****************************************************************************/

int 
Checkconnect (void *readdat)
{
  int conctid, thrdid;
  fd_set fdvar;
  struct timeval timeout;
  int debugflag = 0;

  conctid = ((struct readerdata *) readdat)->conctno;
  thrdid = ((struct readerdata *) readdat)->threadno;
  timeout.tv_sec = 0;           /* Return immediately with value from select  */
  timeout.tv_usec = 0;

  if (conctid == -1)
    { /* conct can be set to -1 when aborted in ReaderThread */
      while (conctid == -1)     /* Reopen connect */
        {
          conctid = connectsrvr ();
        }
      ((struct readerdata *) readdat)->conctno = conctid;
      return (1);               /* Did have to reopen connection    */
    }
  else
    {   /* If connect is supposed to be open, use select to test status  */
      FD_ZERO (&fdvar);
      FD_SET (conctid, &fdvar);
      if (select (conctid + 1, &fdvar, NULL, NULL, &timeout) > 0)
        {
          if ((close (conctid)) < 0)
            printf ("SURGEclient %d: Error closing connect %d\n", systemno, conctid);
          conctid = -1;
          while (conctid == -1) /* Reopen connect */
            {
              conctid = connectsrvr ();
            }
          ((struct readerdata *) readdat)->conctno = conctid;
          if (debugflag)
            {
              printf ("SURGEclient %d: Thrd %d", systemno, thrdid);
              printf (" Reopened conct %d\n", conctid);
            }
          return (1);           /* Did have to reopen connection    */
        }
      else
        {
          return (0);           /* Didn't have to reopen connection */
        }
    }
}  /* End Checkconnect */

/****************************************************************************/
/* This routine is used by the pipelined version of client11.c in order to  */
/* wirte the multiple HTTP requests in a single buffer.  It takes as input  */
/* the reader structure which contains the file names, and where to start   */
/* makeing requests of the files.                                           */
/****************************************************************************/

void 
Writerequest (int start, void *readdat)
{
  int i, thrdid, conctid, numfiles;
  long objindx;
  char wbuff[WRITEBUFFSZ];
  char objbuff[64];
  char url[URLSIZE];
  int debugflag = 0;

  thrdid = ((struct readerdata *) readdat)->threadno;
  objindx = ((struct readerdata *) readdat)->objectno;
  numfiles = ((struct readerdata *) readdat)->nofiles;
  conctid = ((struct readerdata *) readdat)->conctno;

  wbuff[0] = 0;

  /* Generate complete URL string by inserting file number in http string   */
  /* For pipeline requests, place all requests in the write buffer          */

  for (i = start; i < numfiles; i++) {
    bzero (url, URLSIZE);
    strcpy (url, httpstart);
    sprintf (objbuff, "%ld",
             object[objindx][((struct readerdata *) readdat)->fileno[i]]);
    strncat (url, objbuff, sizeof (objbuff));
    strncat (url, httpend, sizeof (httpend));
    if (i == 0) {
      strcpy (wbuff, url);
    } else {
      strncat (wbuff, url, sizeof (url));
    }
    if (!(strlen(wbuff) < WRITEBUFFSZ)) { printf("Consider bigger writebuff\n"); };
  }
  if (debugflag) {
    printf ("SURGEclient %d: Thrd %d Object %ld",
            systemno, thrdid, objindx);
    printf (" Requested fileidxs %d to %d\n", start, numfiles);
  }

  /* Issue HTTP GET call for the specified file to the specified connectin  */
  write (conctid, wbuff, strlen (wbuff));

} /* End Writerequest */
/****************************************************************************/
/*                  Copyright 1997, Trustees of Boston University.          */
/*                               All Rights Reserved.                       */
/*                                                                          */
/* Permission to use, copy, or modify this software and its documentation   */
/* for educational and research purposes only and without fee is hereby     */
/* granted, provided that this copyright notice appear on all copies and    */
/* supporting documentation.  For any other uses of this software, in       */
/* original or modified form, including but not limited to distribution in  */
/* whole or in part, specific prior permission must be obtained from Boston */
/* University.  These programs shall not be used, rewritten, or adapted as  */
/* the basis of a commercial software or hardware product without first     */
/* obtaining appropriate licenses from Boston University.  Boston University*/
/* and the author(s) make no representations about the suitability of this  */
/* software for any purpose.  It is provided "as is" without express or     */
/* implied warranty.                                                        */
/*                                                                          */
/****************************************************************************/
/*                                                                          */
/*  Author:    Paul Barford                                                 */
/*             modified by Min Xu                                           */
/*  Title:     Surgeclient header file                                      */
/*  Revision:  1.0         5/7/98                                           */
/*                                                                          */
/*  With the HTTP/1.1 compliant version of SURGE, it is necessary to make   */
/*  a separate header file with variable definitions that may be used by    */
/*  the clientXX.c files.                                                   */
/*                                                                          */
/*  This is part of the client that use a single client thread for each     */
/*  client.                                                                 */
/*  Currently this file is identical to client11p.h.                        */
/*                                                                          */
/****************************************************************************/
/****************************************************************************/
/* These are the values which you might want to alter for each SURGE test.  */
/* These will go into a config file at some point.                          */
/* NOTE:  You can be sure that you have your SIZE values properly set by    */
/* insuring that NAMESIZE >= wc objout.txt and OUTSIZE >= total requests for*/
/* files (shown after running zipf.c) and MATCHSIZE >= wc mout.txt and      */
/* OBJLIMIT >= LIMIT from object.c and URLSIZE >= max size of all chars  */
/* which will make up any HTTP command string.  READBUFFSZ is the size of   */
/* the buffer that accepts data from the server.                            */
/****************************************************************************/

#define READBUFFSZ  8192       /* size of buffer that data is read into    */
#define WRITEBUFFSZ 8192       /* size of buffer that requests are written */

/****************************************************************************/
/* Define the structures used for passing values to the reader routine      */
/****************************************************************************/

struct readerdata {        /* This is the data passed to each reader thread */
  int conctno;             /* Socket connection for this reader             */
  int threadno;            /* calling thread id                             */
  int nofiles;             /* number of files to be retrieved in pipeline   */
  long objectno;           /* index of object selected                      */
  long fileno[OBJLIMIT];   /* index of file selected                        */
};

Reply via email to