3.27.08 Load Test AJAX Applications With HTTPERF

First off - never leave home before launching any site or server architecture without HTTPERF. http://www.hpl.hp.com/research/linux/httperf/

This is the most robust and elegantly simple load testing CLI (command line interface) I’ve yet ever seen or been happy to use. The one feature that is most impressive is the –wsesslog feature:

      --wsesslog=N,X,F
              This  specifies  a session workload generator similar to --wsess
              (please read that description first).  With  --wsesslog  though,
              many aspects of user sessions, including the number and sequence
              of URI's, request method, think-time  and  burst-length  parame-
              ters, can be specified in an input file F.  Two other parameters
              are retained from --wsess, namely N, the number of  sessions  to
              initiate,  and  X, the burst-to-burst user think time (note that
              this becomes a default time since the  input  file  F  can  also
              specify  user  think time on a per-burst basis.  A small example
              input file can most-easily show the settable parameters:

              # Comment lines start with a ``#'' as the first
              # character.  Lines with only whitespace delimit
              # sessions (multiple blank lines do not generate
              # ``null'' sessions).  All other lines specify a
              # uri-sequence (1 uri per line).  If the first
              # character of the line is whitespace (e.g. space
              # or tab), the uri is considered to be part of a
              # burst that is sent out after the previous
              # non-burst uri.

              # session 1 definition (this is a comment)
              /foo.html think=2.0
                   /pict1.gif
                   /pict2.gif
              /foo2.html method=POST contents='Post data'
                   /pict3.gif
                   /pict4.gif

              # session 2 definition
              /foo3.html method=POST contents="Multiline\ndata"
              /foo4.html method=HEAD

              The above description specifies 2 sessions.  The  first  session
              will  start  with  a  request for /foo.html.  When the /foo.html
              response  comes  back,  a  burst  of  2  requests  will   follow
              (/pict1.gif  and  /pict2.gif).  When the last of those responses
              is received, a two second user think time is inserted before the
              next request of /foo2.html is issued.  This request is sent as a
              POST.  The posted data can be contained between single- or  dou-
              ble-quotes.  Newlines can appear within posted data as ``\n'' or
              as a ``\<CR>''.  The /foo2.html response is followed by a  burst
              request  of /pict3.gif and /pict4.gif, which concludes this ses-
              sion.  The second session is started some time after the  first,
              as specified by the --rate or --period options.

              The  second  session  consists  of  2  requests separated by the
              default user think time as specified by the X parameter  of  the
              --wsesslog  option.  If the N parameter of --wsesslog is greater
              than the number of sessions defined in input file  F,  then  the
              defined  sessions are used repeatedly until N sessions have been
              created (i.e., the defined sessions are used  in  a  round-robin
              fashion).

              One  should  avoid  using  --wsesslog  in conjunction with other
              httperf options that also control session behavior and  workload
              URI's, namely --burst-length, --wsess, --wlog, and --wset.

Download this code: wsesslog.man

This essentially is a very robust way to teach HTTPERF how to simulate user “sessions” or use-cases. This essentially can contain any combination of HTTP GETs or POSTs among other commands. Considering most AJAX frameworks utilize a HTTP POST for sending data this route of testing might seem easy. The one shortcoming with httperf-0.9.0 (latest as of this writing) is that it’s missing a specific request header for each HTTP POST to simulate the AJAX request:
Content-Type: application/x-www-form-urlencoded

I’ve put together a quick patch that you can apply to the wsesslog.c file to enable you to script specific request headers for any individual request in the wsesslog script. Case in point here’s a snippet including the newly added headers=X argument:

/index.cfm
/site_common/style.css
/core.js

/ajax_handler method=POST headers='Content-Type: application/x-www-form-urlencoded\n' contents='ajax=XML&func=TESTFUNC'

Here’s the patch:

--- wsesslog.c  2007-04-07 00:01:56.000000000 -0700
+++ httperf-0.9.0-wsesslog-headers.c    2008-03-27 18:23:02.000000000 -0800
@@ -111,7 +111,7 @@
     int uri_len;
     char *contents;
     int contents_len;
-    char extra_hdrs[50];   /* plenty for "Content-length: 1234567890" */
+    char extra_hdrs[1000]; /* plenty for "Content-length: 1234567890" + custom headers */
     int extra_hdrs_len;
   };
 
@@ -386,6 +386,7 @@
   char line[10000];    /* some uri's get pretty long */
   char uri[10000]; /* some uri's get pretty long */
   char method_str[1000];
+  char headers_str[1000]; /* for capturing extra headers */
   char this_arg[10000];
   char contents[10000];
   double think_time;
@@ -461,6 +462,103 @@
        }
      else if (sscanf (this_arg, "think=%lf", &think_time) == 1)
        current_burst->user_think_time = think_time;
+     else if (sscanf (this_arg, "headers=%s", headers_str) == 1)
+       {
+         /* this is tricky since headers might be a quoted
+        string with embedded spaces or escaped quotes.  We
+        should parse this carefully from parsed_so_far */
+         from = strchr (parsed_so_far, '=') + 1;
+         to = headers_str;
+         single_quoted = FALSE;
+         double_quoted = FALSE;
+         escaped = FALSE;
+         done = FALSE;
+         while ((ch = *from++) != '\0' && !done)
+       {
+         if (escaped == TRUE)
+           {
+             switch (ch)
+           {
+           case 'n':
+             *to++ = '\n';
+             break;
+           case 'r':
+             *to++ = '\r';
+             break;
+           case 't':
+             *to++ = '\t';
+             break;
+           case '\n':
+             *to++ = '\n';
+             /* this allows an escaped newline to
+                continue the parsing to the next line. */
+             if (fgets(line,sizeof(line),fp) == NULL)
+               {
+                 lineno++;
+                 panic ("%s: premature EOF seen in '%s'\n",
+                    prog_name, param.wsesslog.file);  
+               }
+             parsed_so_far = from = line;
+             break;
+           default:
+             *to++ = ch;
+             break;
+           }
+             escaped = FALSE;
+           }
+         else if (ch == '"' && double_quoted)
+           {
+             double_quoted = FALSE;
+           }
+         else if (ch == '\'' && single_quoted)
+           {
+             single_quoted = FALSE;
+           }
+         else
+           {
+             switch (ch)
+           {
+           case '\t':
+           case '\n':
+           case ' ':
+             if (single_quoted == FALSE &&
+                 double_quoted == FALSE)
+               done = TRUE;    /* we are done */
+             else
+               *to++ = ch;
+             break;
+           case '\\':      /* backslash */
+             escaped = TRUE;
+             break;
+           case '"':       /* double quote */
+             if (single_quoted)
+               *to++ = ch;
+             else
+               double_quoted = TRUE;
+             break;
+           case '\'':      /* single quote */
+             if (double_quoted)
+               *to++ = ch;
+             else
+               single_quoted = TRUE;
+             break;
+           default:
+             *to++ = ch;
+             break;
+           }
+           }
+       }
+         *to = '\0';
+         from--;       /* back up 'from' to '\0' or white-space */
+         bytes_read = from - parsed_so_far;
+
+     snprintf (sptr->current_req->extra_hdrs,
+           sizeof(sptr->current_req->extra_hdrs),
+           strdup(headers_str));
+     sptr->current_req->extra_hdrs_len =
+       strlen (sptr->current_req->extra_hdrs);
+
+       }
      else if (sscanf (this_arg, "contents=%s", contents) == 1)
        {
          /* this is tricky since contents might be a quoted
@@ -553,9 +651,11 @@
          if ((sptr->current_req->contents_len = strlen (contents)) != 0)
        {
          sptr->current_req->contents = strdup (contents);
+         /* append content length to the end of existing headers */
          snprintf (sptr->current_req->extra_hdrs,
                sizeof(sptr->current_req->extra_hdrs),
-               "Content-length: %d\r\n",
+               "%sContent-length: %d\r\n",
+              strdup(sptr->current_req->extra_hdrs),
               sptr->current_req->contents_len);
          sptr->current_req->extra_hdrs_len =
            strlen (sptr->current_req->extra_hdrs);

Download this code: httperf-0.9.0-wsesslog-headers.patch

Save the patch as httperf-0.9.0/src/gen/httperf-0.9.0-wsesslog-headers.patch and apply the patch:
# cd httperf-0.9.0/src/gen
# patch < ~/httperf-0.9.0-wsesslog-headers.patch
patching file wsesslog.c


No comments




0.585s